diff --git a/Documentation/CSSGeneratedFiles.md b/Documentation/CSSGeneratedFiles.md index d706d594c5751..1dd7b56dbaa16 100644 --- a/Documentation/CSSGeneratedFiles.md +++ b/Documentation/CSSGeneratedFiles.md @@ -281,13 +281,14 @@ The generated code provides: - `bool is_element_backed_pseudo_element(PseudoElement)` returns whether the pseudo-element is element-backed - `bool is_tree_abiding_pseudo_element(PseudoElement)` returns whether the pseudo-element is tree-abiding - `bool is_pseudo_element_root(PseudoElement)` returns whether the pseudo-element is a [pseudo-element root](https://drafts.csswg.org/css-view-transitions/#pseudo-element-root) -- `bool pseudo_element_supports_property(PseudoElement, PropertyID)` returns whether the property can be applied to this pseudo-element ### `property-whitelist` This is an array of strings. Properties can be named directly ("color"), or categories of properties with a leading `#` ("#font-properties"), as the specs often says a group is allowed instead of listing the properties exactly. Any properties we don't support yet can be prefixed with "FIXME:" and will be ignored. +The property groups and the properties that always apply are defined in +`PseudoElementPropertyGroups.txt` and generated into the Rust cascade tables. The following categories are supported: diff --git a/Libraries/LibWeb/Animations/Animation.cpp b/Libraries/LibWeb/Animations/Animation.cpp index 64e9aa0d1689a..d839d7d119fcd 100644 --- a/Libraries/LibWeb/Animations/Animation.cpp +++ b/Libraries/LibWeb/Animations/Animation.cpp @@ -58,7 +58,7 @@ GC::Ref Animation::construct_impl(JS::Realm& realm, GC::Ptr new_effect) +void Animation::set_effect(GC::Ptr new_effect, ShouldInvalidate should_invalidate) { // Setting this attribute updates the object’s associated effect using the procedure to set the associated effect of // an animation. @@ -106,7 +106,7 @@ void Animation::set_effect(GC::Ptr new_effect) // 7. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, // and the synchronously notify flag set to false. - update_finished_state(DidSeek::No, SynchronouslyNotify::No); + update_finished_state(DidSeek::No, SynchronouslyNotify::No, should_invalidate); } GC::Ptr Animation::timeline_for_bindings() const @@ -809,16 +809,16 @@ WebIDL::ExceptionOr Animation::finish() } // https://www.w3.org/TR/web-animations-1/#dom-animation-play -WebIDL::ExceptionOr Animation::play() +WebIDL::ExceptionOr Animation::play(ShouldInvalidate should_invalidate) { // Begins or resumes playback of the animation by running the procedure to play an animation passing true as the // value of the auto-rewind flag. - return play_an_animation(AutoRewind::Yes); + return play_an_animation(AutoRewind::Yes, should_invalidate); } // https://drafts.csswg.org/web-animations-1/#playing-an-animation-section // https://drafts.csswg.org/web-animations-2/#play-an-animation -WebIDL::ExceptionOr Animation::play_an_animation(AutoRewind auto_rewind) +WebIDL::ExceptionOr Animation::play_an_animation(AutoRewind auto_rewind, ShouldInvalidate should_invalidate) { // 1. Let aborted pause be a boolean flag that is true if animation has a pending pause task, and false otherwise. auto aborted_pause = m_pending_pause_task == TaskState::Scheduled; @@ -921,7 +921,7 @@ WebIDL::ExceptionOr Animation::play_an_animation(AutoRewind auto_rewind) // 13. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false, // and the synchronously notify flag set to false. - update_finished_state(DidSeek::No, SynchronouslyNotify::No); + update_finished_state(DidSeek::No, SynchronouslyNotify::No, should_invalidate); return {}; } @@ -1284,7 +1284,7 @@ WebIDL::ExceptionOr Animation::silently_set_current_time(Optionalrealm(); @@ -1442,7 +1442,8 @@ void Animation::update_finished_state(DidSeek did_seek, SynchronouslyNotify sync m_is_finished = false; } - invalidate_effect(); + if (should_invalidate == ShouldInvalidate::Yes) + invalidate_effect(); } // https://www.w3.org/TR/web-animations-1/#animation-reset-an-animations-pending-tasks diff --git a/Libraries/LibWeb/Animations/Animation.h b/Libraries/LibWeb/Animations/Animation.h index 6096d20e3c41b..af40b72249311 100644 --- a/Libraries/LibWeb/Animations/Animation.h +++ b/Libraries/LibWeb/Animations/Animation.h @@ -30,6 +30,11 @@ class Animation : public DOM::EventTarget { GC_DECLARE_ALLOCATOR(Animation); public: + enum class ShouldInvalidate { + Yes, + No, + }; + static constexpr bool OVERRIDES_FINALIZE = true; static GC::Ref create(JS::Realm&, GC::Ptr, Optional>); @@ -39,7 +44,7 @@ class Animation : public DOM::EventTarget { void set_id(Utf16FlyString value) { m_id = move(value); } GC::Ptr effect() const { return m_effect; } - void set_effect(GC::Ptr); + void set_effect(GC::Ptr, ShouldInvalidate = ShouldInvalidate::Yes); GC::Ptr timeline() const { return m_timeline; } void set_timeline(GC::Ptr); @@ -97,14 +102,10 @@ class Animation : public DOM::EventTarget { Yes, No, }; - enum class ShouldInvalidate { - Yes, - No, - }; void cancel(ShouldInvalidate = ShouldInvalidate::Yes); WebIDL::ExceptionOr finish(); - WebIDL::ExceptionOr play(); - WebIDL::ExceptionOr play_an_animation(AutoRewind); + WebIDL::ExceptionOr play(ShouldInvalidate = ShouldInvalidate::Yes); + WebIDL::ExceptionOr play_an_animation(AutoRewind, ShouldInvalidate = ShouldInvalidate::Yes); WebIDL::ExceptionOr pause(); WebIDL::ExceptionOr update_playback_rate(double); WebIDL::ExceptionOr reverse(); @@ -163,7 +164,7 @@ class Animation : public DOM::EventTarget { void apply_any_pending_playback_rate(); WebIDL::ExceptionOr silently_set_current_time(Optional); - void update_finished_state(DidSeek, SynchronouslyNotify); + void update_finished_state(DidSeek, SynchronouslyNotify, ShouldInvalidate = ShouldInvalidate::Yes); void reset_an_animations_pending_tasks(); bool is_ready() const; diff --git a/Libraries/LibWeb/Animations/AnimationEffect.cpp b/Libraries/LibWeb/Animations/AnimationEffect.cpp index f7a71225e10b6..6ffa30170e5f5 100644 --- a/Libraries/LibWeb/Animations/AnimationEffect.cpp +++ b/Libraries/LibWeb/Animations/AnimationEffect.cpp @@ -859,6 +859,8 @@ AnimationUpdateContext::~AnimationUpdateContext() continue; auto& element = it.key; GC::Ref target = element.element(); + if (!it.value.effects.is_empty()) + target->document().style_computer().collect_animations_into(element, it.value.effects.span(), *style); auto animated_properties_after_update = style->animated_properties_snapshot(); auto invalidation = compute_required_invalidation_for_animated_properties(it.value.animated_properties_before_update.ptr(), animated_properties_after_update.ptr()); diff --git a/Libraries/LibWeb/Animations/AnimationEffect.h b/Libraries/LibWeb/Animations/AnimationEffect.h index 5c58bd853b003..22bde881ceae8 100644 --- a/Libraries/LibWeb/Animations/AnimationEffect.h +++ b/Libraries/LibWeb/Animations/AnimationEffect.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ struct AnimationUpdateContext { RefPtr animated_properties_before_update; RefPtr target_style; + GC::ConservativeVector> effects; }; AnimationUpdateContext(); diff --git a/Libraries/LibWeb/Animations/KeyframeEffect.cpp b/Libraries/LibWeb/Animations/KeyframeEffect.cpp index ded63e5890eb4..06b3b146d3f46 100644 --- a/Libraries/LibWeb/Animations/KeyframeEffect.cpp +++ b/Libraries/LibWeb/Animations/KeyframeEffect.cpp @@ -834,9 +834,14 @@ Optional KeyframeEffect::target_abstract_element() const return {}; } -void KeyframeEffect::set_target(DOM::AbstractElement abstract_element) +void KeyframeEffect::set_target(DOM::AbstractElement abstract_element, InvalidateEffect invalidate) { - set_target(&abstract_element.element()); + if (invalidate == InvalidateEffect::Yes) { + set_target(&abstract_element.element()); + } else { + VERIFY(!associated_animation()); + m_target_element = &abstract_element.element(); + } m_target_pseudo_selector = abstract_element.pseudo_element().map([](auto it) { return CSS::Selector::PseudoElementSelector { it }; }); } @@ -995,7 +1000,7 @@ void KeyframeEffect::update_computed_properties_for_style(AnimationUpdateContext }); VERIFY(element_data.target_style); - style_computer.collect_animation_into(abstract_element, *this, *element_data.target_style); + element_data.effects.append(*this); } Bindings::CompositeOperation css_animation_composition_to_bindings_composite_operation(CSS::AnimationComposition composition) diff --git a/Libraries/LibWeb/Animations/KeyframeEffect.h b/Libraries/LibWeb/Animations/KeyframeEffect.h index ea6eb029c9c25..2277dae959456 100644 --- a/Libraries/LibWeb/Animations/KeyframeEffect.h +++ b/Libraries/LibWeb/Animations/KeyframeEffect.h @@ -90,7 +90,11 @@ class KeyframeEffect final : public AnimationEffect { WebIDL::ExceptionOr set_pseudo_element(Optional); Optional target_abstract_element() const; - void set_target(DOM::AbstractElement); + enum class InvalidateEffect { + No, + Yes, + }; + void set_target(DOM::AbstractElement, InvalidateEffect = InvalidateEffect::Yes); Optional pseudo_element_type() const; void set_pseudo_element(Optional pseudo_element) { m_target_pseudo_selector = pseudo_element; } diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index b63963c0f642d..e548cf25935a1 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -163,7 +163,6 @@ set(SOURCES CSS/CSSUnparsedValue.cpp CSS/CSSVariableReferenceValue.cpp CSS/ColorFunctionDescriptor.cpp - CSS/ColorInterpolation.cpp CSS/CustomPropertyData.cpp CSS/Descriptor.cpp CSS/Display.cpp @@ -201,7 +200,6 @@ set(SOURCES CSS/Invalidation/SlotInvalidator.cpp CSS/Invalidation/StyleInvalidator.cpp CSS/Invalidation/StructuralMutationInvalidator.cpp - CSS/Interpolation.cpp CSS/InvalidationSet.cpp CSS/Length.cpp CSS/LengthBox.cpp @@ -234,8 +232,6 @@ set(SOURCES CSS/Parser/ValueParsing.cpp CSS/Percentage.cpp CSS/PreferredColorScheme.cpp - CSS/PreferredContrast.cpp - CSS/PreferredMotion.cpp CSS/Ratio.cpp CSS/Resolution.cpp CSS/Screen.cpp @@ -244,7 +240,6 @@ set(SOURCES CSS/SelectorRustBridge.cpp CSS/SelectorMatching.cpp CSS/Serialize.cpp - CSS/Size.cpp CSS/Sizing.cpp CSS/StyleComputer.cpp CSS/StyleInvalidation.cpp diff --git a/Libraries/LibWeb/CSS/CSSCounterStyleRule.h b/Libraries/LibWeb/CSS/CSSCounterStyleRule.h index 6f51dc5ecf16d..d358b5cea6391 100644 --- a/Libraries/LibWeb/CSS/CSSCounterStyleRule.h +++ b/Libraries/LibWeb/CSS/CSSCounterStyleRule.h @@ -64,8 +64,6 @@ class CSSCounterStyleRule : public CSSRule { Utf16String speak_as() const; void set_speak_as(Utf16String const& speak_as); - RefPtr const& speak_as_style_value() const { return m_speak_as; } - // https://drafts.csswg.org/css-counter-styles-3/#non-overridable-counter-style-names static bool matches_non_overridable_counter_style_name(Utf16View name) { diff --git a/Libraries/LibWeb/CSS/CSSImportRule.cpp b/Libraries/LibWeb/CSS/CSSImportRule.cpp index 08eab0aced8b7..727bab8149e96 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.cpp +++ b/Libraries/LibWeb/CSS/CSSImportRule.cpp @@ -298,18 +298,6 @@ Optional CSSImportRule::supports_text() const return m_supports->to_string(); } -Optional const& CSSImportRule::scope_start_selectors() const -{ - VERIFY(m_scope.has_value()); - return m_scope->start_selectors; -} - -Optional const& CSSImportRule::scope_end_selectors() const -{ - VERIFY(m_scope.has_value()); - return m_scope->end_selectors; -} - Optional const& CSSImportRule::scope_start_selectors_for_matching() const { VERIFY(m_scope.has_value()); diff --git a/Libraries/LibWeb/CSS/CSSImportRule.h b/Libraries/LibWeb/CSS/CSSImportRule.h index 7b564d8d51294..fab5b3eb8ea31 100644 --- a/Libraries/LibWeb/CSS/CSSImportRule.h +++ b/Libraries/LibWeb/CSS/CSSImportRule.h @@ -49,8 +49,6 @@ class WEB_API CSSImportRule final bool matches() const; bool has_scope() const { return m_scope.has_value(); } - Optional const& scope_start_selectors() const; - Optional const& scope_end_selectors() const; Optional const& scope_start_selectors_for_matching() const; Optional const& scope_end_selectors_for_matching() const; diff --git a/Libraries/LibWeb/CSS/CSSNamespaceRule.h b/Libraries/LibWeb/CSS/CSSNamespaceRule.h index e207c33864b02..93d3b2d10abd1 100644 --- a/Libraries/LibWeb/CSS/CSSNamespaceRule.h +++ b/Libraries/LibWeb/CSS/CSSNamespaceRule.h @@ -20,7 +20,6 @@ class CSSNamespaceRule final : public CSSRule { virtual ~CSSNamespaceRule() = default; - void set_namespace_uri(Utf16FlyString value) { m_namespace_uri = move(value); } Utf16FlyString const& namespace_uri() const { return m_namespace_uri; } void set_prefix(Utf16FlyString value) { m_prefix = move(value); } Utf16FlyString const& prefix() const { return m_prefix; } diff --git a/Libraries/LibWeb/CSS/CSSPropertyRule.h b/Libraries/LibWeb/CSS/CSSPropertyRule.h index 31c4fe75edfe8..1c6f19798e4b0 100644 --- a/Libraries/LibWeb/CSS/CSSPropertyRule.h +++ b/Libraries/LibWeb/CSS/CSSPropertyRule.h @@ -29,8 +29,6 @@ class CSSPropertyRule final : public CSSRule { Utf16FlyString const& syntax() const { return m_syntax; } bool inherits() const { return m_inherits; } Optional initial_value() const; - RefPtr initial_style_value() const { return m_initial_value; } - CustomPropertyRegistration to_registration() const; private: diff --git a/Libraries/LibWeb/CSS/CSSScopeRule.cpp b/Libraries/LibWeb/CSS/CSSScopeRule.cpp index d7063de94c7e7..13bc4571422a2 100644 --- a/Libraries/LibWeb/CSS/CSSScopeRule.cpp +++ b/Libraries/LibWeb/CSS/CSSScopeRule.cpp @@ -40,7 +40,6 @@ void CSSScopeRule::initialize(JS::Realm& realm) void CSSScopeRule::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); - visitor.visit(m_cached_nearest_ancestor_scope_rule); } Optional CSSScopeRule::start() const @@ -150,28 +149,11 @@ GC::Ptr nearest_ancestor_scope_rule_for_matching(CSSRule const& s return nearest_scoped_owner_import(scope_rule.parent_style_sheet()); } -GC::Ptr CSSScopeRule::nearest_ancestor_scope_rule() const -{ - if (m_cached_nearest_ancestor_scope_rule.has_value()) - return m_cached_nearest_ancestor_scope_rule.value(); - - for (auto const* parent = parent_rule(); parent; parent = parent->parent_rule()) { - if (auto const* scope_rule = as_if(parent)) { - m_cached_nearest_ancestor_scope_rule = scope_rule; - return m_cached_nearest_ancestor_scope_rule.value(); - } - } - - m_cached_nearest_ancestor_scope_rule = nullptr; - return m_cached_nearest_ancestor_scope_rule.value(); -} - void CSSScopeRule::clear_caches() { Base::clear_caches(); m_cached_start_selectors_for_matching.clear(); m_cached_end_selectors_for_matching.clear(); - m_cached_nearest_ancestor_scope_rule.clear(); } // https://drafts.csswg.org/cssom-1/#serialize-a-css-rule diff --git a/Libraries/LibWeb/CSS/CSSScopeRule.h b/Libraries/LibWeb/CSS/CSSScopeRule.h index 598c71c0d7188..0a530b4d2d0cf 100644 --- a/Libraries/LibWeb/CSS/CSSScopeRule.h +++ b/Libraries/LibWeb/CSS/CSSScopeRule.h @@ -28,8 +28,6 @@ class CSSScopeRule final : public CSSGroupingRule { Optional const& end_selectors() const { return m_end_selectors; } Optional const& start_selectors_for_matching() const; Optional const& end_selectors_for_matching() const; - GC::Ptr nearest_ancestor_scope_rule() const; - Optional start() const; Optional end() const; @@ -46,7 +44,6 @@ class CSSScopeRule final : public CSSGroupingRule { Optional m_end_selectors; mutable Optional m_cached_start_selectors_for_matching; mutable Optional m_cached_end_selectors_for_matching; - mutable Optional> m_cached_nearest_ancestor_scope_rule; }; template<> diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp index fe6c5c6fb3d32..1bd01071100be 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.cpp +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.cpp @@ -525,7 +525,7 @@ Optional CSSStyleProperties::get_property_internal(PropertyNameAn auto const& original_shorthand_value = list.first()->as_pending_substitution().original_shorthand_value(); auto all_from_same_original = all_of(list, [&](auto const& value) { return value->is_pending_substitution() - && &value->as_pending_substitution().original_shorthand_value() == &original_shorthand_value; + && value->as_pending_substitution().original_shorthand_value().rust_style_value_data() == original_shorthand_value.rust_style_value_data(); }); if (all_from_same_original) { return StyleProperty { @@ -973,27 +973,27 @@ RefPtr CSSStyleProperties::style_value_for_computed_property(L // none or contents, and the property is not over-constrained, then the resolved value is the used value. // Otherwise the resolved value is the computed value. case PropertyID::Bottom: { - auto& inset = layout_node.computed_values().inset(); + auto inset = layout_node.computed_values().inset(); if (auto maybe_used_value = used_value_for_inset(inset.bottom(), inset.top(), [](auto const& paintable_box) { return paintable_box.box_model().inset.bottom; }); maybe_used_value.has_value()) return LengthStyleValue::create(Length::make_px(maybe_used_value.release_value())); return style_value_for_length_percentage_or_auto(inset.bottom()); } case PropertyID::Left: { - auto& inset = layout_node.computed_values().inset(); + auto inset = layout_node.computed_values().inset(); if (auto maybe_used_value = used_value_for_inset(inset.left(), inset.right(), [](auto const& paintable_box) { return paintable_box.box_model().inset.left; }); maybe_used_value.has_value()) return LengthStyleValue::create(Length::make_px(maybe_used_value.release_value())); return style_value_for_length_percentage_or_auto(inset.left()); } case PropertyID::Right: { - auto& inset = layout_node.computed_values().inset(); + auto inset = layout_node.computed_values().inset(); if (auto maybe_used_value = used_value_for_inset(inset.right(), inset.left(), [](auto const& paintable_box) { return paintable_box.box_model().inset.right; }); maybe_used_value.has_value()) return LengthStyleValue::create(Length::make_px(maybe_used_value.release_value())); return style_value_for_length_percentage_or_auto(inset.right()); } case PropertyID::Top: { - auto& inset = layout_node.computed_values().inset(); + auto inset = layout_node.computed_values().inset(); if (auto maybe_used_value = used_value_for_inset(inset.top(), inset.bottom(), [](auto const& paintable_box) { return paintable_box.box_model().inset.top; }); maybe_used_value.has_value()) return LengthStyleValue::create(Length::make_px(maybe_used_value.release_value())); diff --git a/Libraries/LibWeb/CSS/CSSStyleProperties.h b/Libraries/LibWeb/CSS/CSSStyleProperties.h index b4d71023e27c8..f8f8f092912e1 100644 --- a/Libraries/LibWeb/CSS/CSSStyleProperties.h +++ b/Libraries/LibWeb/CSS/CSSStyleProperties.h @@ -49,8 +49,6 @@ class WEB_API CSSStyleProperties Vector const& properties() const { return m_properties; } OrderedHashMap const& custom_properties() const { return m_custom_properties; } - size_t custom_property_count() const { return m_custom_properties.size(); } - virtual bool has_property(PropertyNameAndID const&) const override; bool has_property(PropertyID) const; diff --git a/Libraries/LibWeb/CSS/CSSTransition.cpp b/Libraries/LibWeb/CSS/CSSTransition.cpp index 83ed7ad0f138a..285019e3e33fc 100644 --- a/Libraries/LibWeb/CSS/CSSTransition.cpp +++ b/Libraries/LibWeb/CSS/CSSTransition.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -118,7 +117,9 @@ CSSTransition::CSSTransition( // that have been disassociated from their owning element but are still idle do not have a defined composite order. // Construct a KeyframesEffect for our animation - m_keyframe_effect->set_target(abstract_element); + // NB: The current style computation collects this effect before publishing its result, so scheduling a second + // animated style update here would evaluate the same transition twice. + m_keyframe_effect->set_target(abstract_element, Animations::KeyframeEffect::InvalidateEffect::No); m_keyframe_effect->set_specified_start_delay(delay); m_keyframe_effect->set_specified_iteration_duration(end_time - start_time); // AD-HOC: CSS Transitions require the start value to apply during transition-delay. A default KeyframeEffect does @@ -144,11 +145,11 @@ CSSTransition::CSSTransition( m_keyframe_effect->set_key_frame_set(key_frame_set); set_timeline(abstract_element.document().timeline()); set_owning_element(abstract_element); - set_effect(m_keyframe_effect); + set_effect(m_keyframe_effect, Animations::Animation::ShouldInvalidate::No); abstract_element.element().set_transition(abstract_element.pseudo_element(), m_transition_property, *this); HTML::TemporaryExecutionContext context(realm); - play().release_value_but_fixme_should_propagate_errors(); + play(Animations::Animation::ShouldInvalidate::No).release_value_but_fixme_should_propagate_errors(); } void CSSTransition::initialize(JS::Realm& realm) diff --git a/Libraries/LibWeb/CSS/CSSTransition.h b/Libraries/LibWeb/CSS/CSSTransition.h index 6324352cfbe1b..5577603018c98 100644 --- a/Libraries/LibWeb/CSS/CSSTransition.h +++ b/Libraries/LibWeb/CSS/CSSTransition.h @@ -8,7 +8,6 @@ #pragma once #include -#include #include namespace Web::CSS { @@ -37,7 +36,6 @@ class CSSTransition : public Animations::Animation { double transition_start_time() const { return m_start_time; } double transition_end_time() const { return m_end_time; } - NonnullRefPtr transition_start_value() const { return m_start_value; } NonnullRefPtr transition_end_value() const { return m_end_value; } NonnullRefPtr reversing_adjusted_start_value() const { return m_reversing_adjusted_start_value; } double reversing_shortening_factor() const { return m_reversing_shortening_factor; } diff --git a/Libraries/LibWeb/CSS/CascadedProperties.cpp b/Libraries/LibWeb/CSS/CascadedProperties.cpp index ba0f7c340df50..46b78db82add3 100644 --- a/Libraries/LibWeb/CSS/CascadedProperties.cpp +++ b/Libraries/LibWeb/CSS/CascadedProperties.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -43,9 +44,30 @@ void CascadedProperties::assign_source_slot(u32 slot, GC::Ptr CascadedProperties::source_for_slot(u32 slot) const +{ + if (slot >= m_source_slots.size()) + return nullptr; + return m_source_slots[slot].source.ptr(); +} + RefPtr CascadedProperties::property(PropertyID property_id) const { - return static_cast(ComputedValuesFFI::rust_cascaded_properties_property(m_store, to_underlying(property_id))); + auto const* data = static_cast(ComputedValuesFFI::rust_cascaded_properties_property(m_store, to_underlying(property_id))); + if (!data) { + m_property_cache.remove(property_id); + return nullptr; + } + if (auto it = m_property_cache.find(property_id); it != m_property_cache.end() && it->value->rust_style_value_data() == data) + return it->value; + auto value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(data)); + auto source_slot = ComputedValuesFFI::rust_cascaded_properties_source_slot(m_store, to_underlying(property_id)); + if (source_slot >= 0 && ComputedValuesFFI::rust_cascaded_properties_has_style_sheet_context(m_store, to_underlying(property_id))) { + if (auto source = source_for_slot(static_cast(source_slot)); source && source->parent_rule()) + const_cast(*value).set_style_sheet(source->parent_rule()->parent_style_sheet()); + } + m_property_cache.set(property_id, value); + return value; } GC::Ptr CascadedProperties::property_source_shadow_root(PropertyID property_id) const diff --git a/Libraries/LibWeb/CSS/CascadedProperties.h b/Libraries/LibWeb/CSS/CascadedProperties.h index 41170801b6906..5ce049f6157f4 100644 --- a/Libraries/LibWeb/CSS/CascadedProperties.h +++ b/Libraries/LibWeb/CSS/CascadedProperties.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -38,6 +39,7 @@ class CascadedProperties final : public RefCounted { // GC-weak declaration source pair for a slot the store handed out. ComputedValuesFFI::CascadedPropertyStore* rust_store() { return m_store; } void assign_source_slot(u32 slot, GC::Ptr source, GC::Ptr source_shadow_root); + [[nodiscard]] GC::Ptr source_for_slot(u32 slot) const; private: CascadedProperties(); @@ -49,6 +51,7 @@ class CascadedProperties final : public RefCounted { ComputedValuesFFI::CascadedPropertyStore* m_store { nullptr }; Vector m_source_slots; + mutable HashMap> m_property_cache; }; } diff --git a/Libraries/LibWeb/CSS/ColorInterpolation.cpp b/Libraries/LibWeb/CSS/ColorInterpolation.cpp deleted file mode 100644 index 7b606e110f570..0000000000000 --- a/Libraries/LibWeb/CSS/ColorInterpolation.cpp +++ /dev/null @@ -1,1016 +0,0 @@ -/* - * Copyright (c) 2026, Tim Ledbetter - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include - -namespace Web::CSS { - -static float interpolate_color_component(float from, float to, float delta) -{ - return from + (to - from) * delta; -} - -// https://drafts.csswg.org/css-color-4/#hue-interpolation -static void fixup_hue(float& hue1, float& hue2, HueInterpolationMethod hue_interpolation_method) -{ - auto difference = hue2 - hue1; - switch (hue_interpolation_method) { - // https://drafts.csswg.org/css-color-4/#hue-shorter - case HueInterpolationMethod::Shorter: - if (difference > 180.0f) - hue1 += 360.0f; - else if (difference < -180.0f) - hue2 += 360.0f; - break; - // https://drafts.csswg.org/css-color-4/#hue-longer - case HueInterpolationMethod::Longer: - if (difference > 0.0f && difference < 180.0f) - hue1 += 360.0f; - else if (difference > -180.0f && difference <= 0.0f) - hue2 += 360.0f; - break; - // https://drafts.csswg.org/css-color-4/#hue-increasing - case HueInterpolationMethod::Increasing: - if (hue2 < hue1) - hue2 += 360.0f; - break; - // https://drafts.csswg.org/css-color-4/#hue-decreasing - case HueInterpolationMethod::Decreasing: - if (hue1 < hue2) - hue1 += 360.0f; - break; - } -} - -static Gfx::ColorComponents srgb_to_rectangular_color_space(Gfx::ColorComponents srgb, RectangularColorSpace space) -{ - if (space == RectangularColorSpace::Srgb) - return srgb; - if (space == RectangularColorSpace::SrgbLinear) - return Gfx::srgb_to_linear_srgb(srgb); - - auto xyz65 = Gfx::linear_srgb_to_xyz65(Gfx::srgb_to_linear_srgb(srgb)); - switch (space) { - case RectangularColorSpace::Srgb: - case RectangularColorSpace::SrgbLinear: - VERIFY_NOT_REACHED(); - case RectangularColorSpace::DisplayP3: - return Gfx::linear_display_p3_to_display_p3(Gfx::xyz65_to_linear_display_p3(xyz65)); - case RectangularColorSpace::DisplayP3Linear: - return Gfx::xyz65_to_linear_display_p3(xyz65); - case RectangularColorSpace::A98Rgb: - return Gfx::linear_a98_rgb_to_a98_rgb(Gfx::xyz65_to_linear_a98_rgb(xyz65)); - case RectangularColorSpace::ProphotoRgb: - return Gfx::linear_prophoto_rgb_to_prophoto_rgb(Gfx::xyz50_to_linear_prophoto_rgb(Gfx::xyz65_to_xyz50(xyz65))); - case RectangularColorSpace::Rec2020: - return Gfx::linear_rec2020_to_rec2020(Gfx::xyz65_to_linear_rec2020(xyz65)); - case RectangularColorSpace::Lab: - return Gfx::xyz50_to_lab(Gfx::xyz65_to_xyz50(xyz65)); - case RectangularColorSpace::Oklab: - return Gfx::xyz65_to_oklab(xyz65); - case RectangularColorSpace::Xyz: - case RectangularColorSpace::XyzD65: - return xyz65; - case RectangularColorSpace::XyzD50: - return Gfx::xyz65_to_xyz50(xyz65); - } - VERIFY_NOT_REACHED(); -} - -static Gfx::ColorComponents srgb_to_polar_color_space(Gfx::ColorComponents srgb, PolarColorSpace space) -{ - switch (space) { - case PolarColorSpace::Hsl: - return Gfx::srgb_to_hsl(srgb); - case PolarColorSpace::Hwb: - return Gfx::srgb_to_hwb(srgb); - case PolarColorSpace::Lch: { - auto xyz65 = Gfx::linear_srgb_to_xyz65(Gfx::srgb_to_linear_srgb(srgb)); - return Gfx::lab_to_lch(Gfx::xyz50_to_lab(Gfx::xyz65_to_xyz50(xyz65))); - } - case PolarColorSpace::Oklch: { - auto xyz65 = Gfx::linear_srgb_to_xyz65(Gfx::srgb_to_linear_srgb(srgb)); - return Gfx::oklab_to_oklch(Gfx::xyz65_to_oklab(xyz65)); - } - } - VERIFY_NOT_REACHED(); -} - -static bool is_component_none(StyleValue const& component) -{ - return component.to_keyword() == Keyword::None; -} - -static MissingComponents extract_missing_components(StyleValue const& style_value) -{ - if (!style_value.is_color()) - return {}; - - auto const& color = style_value.as_color(); - if (!color.color_type().has_value()) - return {}; - auto const& function = as(color); - auto alpha = function.alpha(); - // An omitted alpha isn't a missing component; it defaults to 1. - bool alpha_is_none = alpha && is_component_none(*alpha); - return { is_component_none(function.channel(0)), is_component_none(function.channel(1)), is_component_none(function.channel(2)), alpha_is_none }; -} - -// https://drafts.csswg.org/css-color-4/#interpolation-missing -// Analogous component categories for carrying forward missing components across color spaces. -// Each input color space component is classified into a category. If a missing component in -// the source space has an analogous component in the interpolation space, it is carried forward. -// https://drafts.csswg.org/css-color-4/#interpolation-missing -static ComponentCategories categories_for_rectangular_space(RectangularColorSpace space) -{ - switch (space) { - case RectangularColorSpace::Srgb: - case RectangularColorSpace::SrgbLinear: - case RectangularColorSpace::DisplayP3: - case RectangularColorSpace::DisplayP3Linear: - case RectangularColorSpace::A98Rgb: - case RectangularColorSpace::ProphotoRgb: - case RectangularColorSpace::Rec2020: - return { ComponentCategory::Red, ComponentCategory::Green, ComponentCategory::Blue }; - case RectangularColorSpace::Xyz: - case RectangularColorSpace::XyzD50: - case RectangularColorSpace::XyzD65: - // NOTE: The spec says XYZ spaces are considered super-saturated RGB for this purpose. - return { ComponentCategory::Red, ComponentCategory::Green, ComponentCategory::Blue }; - case RectangularColorSpace::Lab: - case RectangularColorSpace::Oklab: - return { ComponentCategory::Lightness, ComponentCategory::OpponentA, ComponentCategory::OpponentB }; - } - VERIFY_NOT_REACHED(); -} - -static ComponentCategories categories_for_polar_space(PolarColorSpace space) -{ - switch (space) { - case PolarColorSpace::Hsl: - return { ComponentCategory::Hue, ComponentCategory::Colorfulness, ComponentCategory::Lightness }; - case PolarColorSpace::Hwb: - // NOTE: Whiteness and Blackness have no analogs in other color spaces. - return { ComponentCategory::Hue, ComponentCategory::NotAnalogous, ComponentCategory::NotAnalogous }; - case PolarColorSpace::Lch: - case PolarColorSpace::Oklch: - return { ComponentCategory::Lightness, ComponentCategory::Colorfulness, ComponentCategory::Hue }; - } - VERIFY_NOT_REACHED(); -} - -static ComponentCategories categories_for_color_type(ColorStyleValue::ColorType color_type) -{ - switch (color_type) { - case ColorStyleValue::ColorType::HSL: - return { ComponentCategory::Hue, ComponentCategory::Colorfulness, ComponentCategory::Lightness }; - case ColorStyleValue::ColorType::HWB: - return { ComponentCategory::Hue, ComponentCategory::NotAnalogous, ComponentCategory::NotAnalogous }; - case ColorStyleValue::ColorType::Lab: - case ColorStyleValue::ColorType::OKLab: - return { ComponentCategory::Lightness, ComponentCategory::OpponentA, ComponentCategory::OpponentB }; - case ColorStyleValue::ColorType::LCH: - case ColorStyleValue::ColorType::OKLCH: - return { ComponentCategory::Lightness, ComponentCategory::Colorfulness, ComponentCategory::Hue }; - case ColorStyleValue::ColorType::RGB: - case ColorStyleValue::ColorType::A98RGB: - case ColorStyleValue::ColorType::DisplayP3: - case ColorStyleValue::ColorType::DisplayP3Linear: - case ColorStyleValue::ColorType::sRGB: - case ColorStyleValue::ColorType::sRGBLinear: - case ColorStyleValue::ColorType::ProPhotoRGB: - case ColorStyleValue::ColorType::Rec2020: - case ColorStyleValue::ColorType::XYZD50: - case ColorStyleValue::ColorType::XYZD65: - return { ComponentCategory::Red, ComponentCategory::Green, ComponentCategory::Blue }; - default: - return { ComponentCategory::NotAnalogous, ComponentCategory::NotAnalogous, ComponentCategory::NotAnalogous }; - } -} - -// https://drafts.csswg.org/css-color-4/#interpolation-missing -// Carry forward missing components from the input color space to the interpolation color space. -// A missing component is carried forward if it has an analogous component in the target space. -// Additionally, if ALL components of an analogous set are missing, they are all carried forward. -static MissingComponents carry_forward_missing_components( - MissingComponents const& source_missing, - ComponentCategories const& source_categories, - ComponentCategories const& target_categories) -{ - MissingComponents result; - - // Same-space: all components map to themselves, including NotAnalogous ones (e.g. HWB W/B). - if (source_categories == target_categories) { - for (size_t i = 0; i < 3; ++i) - result.component(i) = source_missing.component(i); - result.alpha = source_missing.alpha; - return result; - } - - // Carry forward individual analogous components - for (size_t target_index = 0; target_index < 3; ++target_index) { - if (target_categories.component(target_index) == ComponentCategory::NotAnalogous) - continue; - for (size_t source_index = 0; source_index < 3; ++source_index) { - if (source_missing.component(source_index) && source_categories.component(source_index) == target_categories.component(target_index)) { - result.component(target_index) = true; - break; - } - } - } - - // If every component of an analogous set is missing in the source, carry forward as a set. - // The analogous set consists of the components that remain after removing individually analogous ones. - bool all_non_analogous_missing = true; - bool has_non_analogous = false; - for (size_t i = 0; i < 3; ++i) { - bool is_individually_analogous = false; - for (size_t j = 0; j < 3; ++j) { - if (source_categories.component(i) != ComponentCategory::NotAnalogous && source_categories.component(i) == target_categories.component(j)) { - is_individually_analogous = true; - break; - } - } - if (!is_individually_analogous) { - has_non_analogous = true; - if (!source_missing.component(i)) - all_non_analogous_missing = false; - } - } - if (has_non_analogous && all_non_analogous_missing) { - for (size_t i = 0; i < 3; ++i) { - bool is_individually_analogous = false; - for (size_t j = 0; j < 3; ++j) { - if (target_categories.component(i) != ComponentCategory::NotAnalogous && target_categories.component(i) == source_categories.component(j)) { - is_individually_analogous = true; - break; - } - } - if (!is_individually_analogous) - result.component(i) = true; - } - } - - // Alpha is always analogous to itself. - result.alpha = source_missing.alpha; - return result; -} - -static ValueComparingNonnullRefPtr number_or_none(float value, bool is_missing) -{ - if (is_missing) - return KeywordStyleValue::create(Keyword::None); - return NumberStyleValue::create(value); -} - -static ValueComparingNonnullRefPtr style_value_from_rectangular_color_space(Gfx::ColorComponents const& components, RectangularColorSpace space, MissingComponents const& missing = {}) -{ - auto c1 = number_or_none(components[0], missing.component(0)); - auto c2 = number_or_none(components[1], missing.component(1)); - auto c3 = number_or_none(components[2], missing.component(2)); - auto alpha = number_or_none(components.alpha(), missing.alpha); - - switch (space) { - case RectangularColorSpace::Lab: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::Lab, c1, c2, c3, alpha); - case RectangularColorSpace::Oklab: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::OKLab, c1, c2, c3, alpha); - case RectangularColorSpace::Srgb: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::sRGB, c1, c2, c3, alpha); - case RectangularColorSpace::SrgbLinear: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::sRGBLinear, c1, c2, c3, alpha); - case RectangularColorSpace::DisplayP3: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::DisplayP3, c1, c2, c3, alpha); - case RectangularColorSpace::DisplayP3Linear: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::DisplayP3Linear, c1, c2, c3, alpha); - case RectangularColorSpace::A98Rgb: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::A98RGB, c1, c2, c3, alpha); - case RectangularColorSpace::ProphotoRgb: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::ProPhotoRGB, c1, c2, c3, alpha); - case RectangularColorSpace::Rec2020: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::Rec2020, c1, c2, c3, alpha); - case RectangularColorSpace::Xyz: - case RectangularColorSpace::XyzD65: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::XYZD65, c1, c2, c3, alpha); - case RectangularColorSpace::XyzD50: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::XYZD50, c1, c2, c3, alpha); - } - VERIFY_NOT_REACHED(); -} - -static ValueComparingNonnullRefPtr style_value_from_polar_color_space(Gfx::ColorComponents const& components, PolarColorSpace space, MissingComponents const& missing = {}) -{ - auto alpha = number_or_none(components.alpha(), missing.alpha); - - switch (space) { - case PolarColorSpace::Hsl: { - // HSL/HWB resolve to sRGB in computed values, so convert and express as color(srgb ...). - auto srgb = Gfx::hsl_to_srgb(components); - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::sRGB, - NumberStyleValue::create(srgb[0]), - NumberStyleValue::create(srgb[1]), - NumberStyleValue::create(srgb[2]), - alpha); - } - case PolarColorSpace::Hwb: { - auto srgb = Gfx::hwb_to_srgb(components); - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::sRGB, - NumberStyleValue::create(srgb[0]), - NumberStyleValue::create(srgb[1]), - NumberStyleValue::create(srgb[2]), - alpha); - } - case PolarColorSpace::Lch: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::LCH, - number_or_none(components[0], missing.component(0)), - number_or_none(components[1], missing.component(1)), - number_or_none(components[2], missing.component(2)), - alpha); - case PolarColorSpace::Oklch: - return ColorFunctionStyleValue::create(ColorStyleValue::ColorType::OKLCH, - number_or_none(components[0], missing.component(0)), - number_or_none(components[1], missing.component(1)), - number_or_none(components[2], missing.component(2)), - alpha); - } - VERIFY_NOT_REACHED(); -} - -static Optional style_value_to_color_components(StyleValue const& style_value, CalculationResolutionContext const& context) -{ - if (!style_value.is_color()) - return {}; - - auto const& color = style_value.as_color(); - auto color_type = color.color_type(); - if (!color_type.has_value()) - return {}; - auto resolve_alpha = [&](ValueComparingRefPtr const& alpha_style_value) -> Optional { - // An omitted alpha on a ColorFunctionStyleValue is treated as 1 for interpolation. - if (!alpha_style_value) - return 1.0f; - auto result = ColorStyleValue::resolve_alpha(*alpha_style_value, context); - if (!result.has_value()) - return {}; - return static_cast(result.value()); - }; - - switch (*color_type) { - case ColorStyleValue::ColorType::HSL: { - auto const& hsl = as(color); - auto h = ColorStyleValue::resolve_hue(hsl.channel(0), context); - auto s = ColorStyleValue::resolve_with_reference_value(hsl.channel(1), 100.0f, context); - auto l = ColorStyleValue::resolve_with_reference_value(hsl.channel(2), 100.0f, context); - auto a = resolve_alpha(hsl.alpha()); - if (!h.has_value() || !s.has_value() || !l.has_value() || !a.has_value()) - return {}; - // ColorConversion expects S and L as fractions (0-1), not percentages - return Gfx::ColorComponents { static_cast(h.value()), static_cast(s.value() / 100.0), static_cast(l.value() / 100.0), a.value() }; - } - case ColorStyleValue::ColorType::HWB: { - auto const& hwb = as(color); - auto h = ColorStyleValue::resolve_hue(hwb.channel(0), context); - auto w = ColorStyleValue::resolve_with_reference_value(hwb.channel(1), 100.0f, context); - auto b = ColorStyleValue::resolve_with_reference_value(hwb.channel(2), 100.0f, context); - auto a = resolve_alpha(hwb.alpha()); - if (!h.has_value() || !w.has_value() || !b.has_value() || !a.has_value()) - return {}; - return Gfx::ColorComponents { static_cast(h.value()), static_cast(w.value() / 100.0), static_cast(b.value() / 100.0), a.value() }; - } - case ColorStyleValue::ColorType::Lab: { - auto const& lab = as(color); - auto l = ColorStyleValue::resolve_with_reference_value(lab.channel(0), 100.0f, context); - auto a_comp = ColorStyleValue::resolve_with_reference_value(lab.channel(1), 125.0f, context); - auto b_comp = ColorStyleValue::resolve_with_reference_value(lab.channel(2), 125.0f, context); - auto a = resolve_alpha(lab.alpha()); - if (!l.has_value() || !a_comp.has_value() || !b_comp.has_value() || !a.has_value()) - return {}; - return Gfx::ColorComponents { static_cast(l.value()), static_cast(a_comp.value()), static_cast(b_comp.value()), a.value() }; - } - case ColorStyleValue::ColorType::OKLab: { - auto const& oklab = as(color); - auto l = ColorStyleValue::resolve_with_reference_value(oklab.channel(0), 1.0f, context); - auto a_comp = ColorStyleValue::resolve_with_reference_value(oklab.channel(1), 0.4f, context); - auto b_comp = ColorStyleValue::resolve_with_reference_value(oklab.channel(2), 0.4f, context); - auto a = resolve_alpha(oklab.alpha()); - if (!l.has_value() || !a_comp.has_value() || !b_comp.has_value() || !a.has_value()) - return {}; - return Gfx::ColorComponents { static_cast(l.value()), static_cast(a_comp.value()), static_cast(b_comp.value()), a.value() }; - } - case ColorStyleValue::ColorType::LCH: { - auto const& lch = as(color); - auto l = ColorStyleValue::resolve_with_reference_value(lch.channel(0), 100.0f, context); - auto c = ColorStyleValue::resolve_with_reference_value(lch.channel(1), 150.0f, context); - auto h = ColorStyleValue::resolve_hue(lch.channel(2), context); - auto a = resolve_alpha(lch.alpha()); - if (!l.has_value() || !c.has_value() || !h.has_value() || !a.has_value()) - return {}; - return Gfx::ColorComponents { static_cast(l.value()), static_cast(c.value()), static_cast(h.value()), a.value() }; - } - case ColorStyleValue::ColorType::OKLCH: { - auto const& oklch = as(color); - auto l = ColorStyleValue::resolve_with_reference_value(oklch.channel(0), 1.0f, context); - auto c = ColorStyleValue::resolve_with_reference_value(oklch.channel(1), 0.4f, context); - auto h = ColorStyleValue::resolve_hue(oklch.channel(2), context); - auto a = resolve_alpha(oklch.alpha()); - if (!l.has_value() || !c.has_value() || !h.has_value() || !a.has_value()) - return {}; - return Gfx::ColorComponents { static_cast(l.value()), static_cast(c.value()), static_cast(h.value()), a.value() }; - } - case ColorStyleValue::ColorType::RGB: { - auto const& rgb = as(color); - auto r = ColorStyleValue::resolve_with_reference_value(rgb.channel(0), 255.0f, context); - auto g = ColorStyleValue::resolve_with_reference_value(rgb.channel(1), 255.0f, context); - auto b = ColorStyleValue::resolve_with_reference_value(rgb.channel(2), 255.0f, context); - auto a = resolve_alpha(rgb.alpha()); - if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value()) - return {}; - // rgb() computed values clamp channels to [0, 255] before normalizing to [0, 1]. - return Gfx::ColorComponents { - static_cast(clamp(r.value(), 0.0, 255.0) / 255.0), - static_cast(clamp(g.value(), 0.0, 255.0) / 255.0), - static_cast(clamp(b.value(), 0.0, 255.0) / 255.0), - a.value(), - }; - } - default: - if (color.is_color_function()) { - auto const& func = as(color); - auto c1 = ColorStyleValue::resolve_with_reference_value(func.channel(0), 1.0f, context); - auto c2 = ColorStyleValue::resolve_with_reference_value(func.channel(1), 1.0f, context); - auto c3 = ColorStyleValue::resolve_with_reference_value(func.channel(2), 1.0f, context); - auto a = resolve_alpha(func.alpha()); - if (!c1.has_value() || !c2.has_value() || !c3.has_value() || !a.has_value()) - return {}; - return Gfx::ColorComponents { static_cast(c1.value()), static_cast(c2.value()), static_cast(c3.value()), a.value() }; - } - return {}; - } -} - -static Gfx::ColorComponents native_components_to_srgb(Gfx::ColorComponents native, ColorStyleValue::ColorType source_type) -{ - switch (source_type) { - case ColorStyleValue::ColorType::RGB: - case ColorStyleValue::ColorType::sRGB: - return native; - case ColorStyleValue::ColorType::sRGBLinear: - return Gfx::linear_srgb_to_srgb(native); - case ColorStyleValue::ColorType::HSL: - return Gfx::hsl_to_srgb(native); - case ColorStyleValue::ColorType::HWB: - return Gfx::hwb_to_srgb(native); - case ColorStyleValue::ColorType::Lab: { - auto xyz50 = Gfx::lab_to_xyz50(native); - auto xyz65 = Gfx::xyz50_to_xyz65(xyz50); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::OKLab: { - auto xyz65 = Gfx::oklab_to_xyz65(native); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::LCH: { - auto lab = Gfx::lch_to_lab(native); - auto xyz50 = Gfx::lab_to_xyz50(lab); - auto xyz65 = Gfx::xyz50_to_xyz65(xyz50); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::OKLCH: { - auto oklab = Gfx::oklch_to_oklab(native); - auto xyz65 = Gfx::oklab_to_xyz65(oklab); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::DisplayP3: { - auto linear_p3 = Gfx::display_p3_to_linear_display_p3(native); - auto xyz65 = Gfx::linear_display_p3_to_xyz65(linear_p3); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::DisplayP3Linear: { - auto xyz65 = Gfx::linear_display_p3_to_xyz65(native); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::A98RGB: { - auto linear_a98 = Gfx::a98_rgb_to_linear_a98_rgb(native); - auto xyz65 = Gfx::linear_a98_rgb_to_xyz65(linear_a98); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::ProPhotoRGB: { - auto linear_prophoto = Gfx::prophoto_rgb_to_linear_prophoto_rgb(native); - auto xyz50 = Gfx::linear_prophoto_rgb_to_xyz50(linear_prophoto); - auto xyz65 = Gfx::xyz50_to_xyz65(xyz50); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::Rec2020: { - auto linear_rec2020 = Gfx::rec2020_to_linear_rec2020(native); - auto xyz65 = Gfx::linear_rec2020_to_xyz65(linear_rec2020); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::XYZD50: { - auto xyz65 = Gfx::xyz50_to_xyz65(native); - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(xyz65)); - } - case ColorStyleValue::ColorType::XYZD65: - return Gfx::linear_srgb_to_srgb(Gfx::xyz65_to_linear_srgb(native)); - default: - VERIFY_NOT_REACHED(); - } -} - -static bool color_type_matches_rectangular_space(ColorStyleValue::ColorType source_type, RectangularColorSpace space) -{ - switch (space) { - case RectangularColorSpace::Srgb: - return source_type == ColorStyleValue::ColorType::sRGB || source_type == ColorStyleValue::ColorType::RGB; - case RectangularColorSpace::SrgbLinear: - return source_type == ColorStyleValue::ColorType::sRGBLinear; - case RectangularColorSpace::DisplayP3: - return source_type == ColorStyleValue::ColorType::DisplayP3; - case RectangularColorSpace::DisplayP3Linear: - return source_type == ColorStyleValue::ColorType::DisplayP3Linear; - case RectangularColorSpace::A98Rgb: - return source_type == ColorStyleValue::ColorType::A98RGB; - case RectangularColorSpace::ProphotoRgb: - return source_type == ColorStyleValue::ColorType::ProPhotoRGB; - case RectangularColorSpace::Rec2020: - return source_type == ColorStyleValue::ColorType::Rec2020; - case RectangularColorSpace::Lab: - return source_type == ColorStyleValue::ColorType::Lab; - case RectangularColorSpace::Oklab: - return source_type == ColorStyleValue::ColorType::OKLab; - case RectangularColorSpace::Xyz: - case RectangularColorSpace::XyzD65: - return source_type == ColorStyleValue::ColorType::XYZD65; - case RectangularColorSpace::XyzD50: - return source_type == ColorStyleValue::ColorType::XYZD50; - } - VERIFY_NOT_REACHED(); -} - -static bool color_type_matches_polar_space(ColorStyleValue::ColorType source_type, PolarColorSpace space) -{ - switch (space) { - case PolarColorSpace::Hsl: - return source_type == ColorStyleValue::ColorType::HSL; - case PolarColorSpace::Hwb: - return source_type == ColorStyleValue::ColorType::HWB; - case PolarColorSpace::Lch: - return source_type == ColorStyleValue::ColorType::LCH; - case PolarColorSpace::Oklch: - return source_type == ColorStyleValue::ColorType::OKLCH; - } - VERIFY_NOT_REACHED(); -} - -// https://drafts.csswg.org/css-color-4/#powerless -static void mark_powerless_for_zero_alpha(bool has_native_components, float alpha, MissingComponents& missing) -{ - if (has_native_components) - return; - if (!missing.alpha && alpha == 0.0f) { - missing.component(0) = true; - missing.component(1) = true; - missing.component(2) = true; - } -} - -// NB: Achromatic colors converted through the sRGB -> XYZ-D65 -> XYZ-D50 -> Lab -> LCH chain accumulate -// floating-point error of ~0.016 in the chroma component due to the Bradford chromatic adaptation matrices. -// This is the worst case for all color conversion types, so the threshold is large enough to account for this. -static constexpr float achromatic_threshold = 0.02f; - -static void mark_powerless_hue_after_conversion( - Gfx::ColorComponents const& components, MissingComponents& interp_missing, - StyleValue const& style_value, PolarColorSpace polar_color_space, - ComponentCategories const& target_categories) -{ - if (style_value.is_color()) { - auto color_type = style_value.as_color().color_type(); - if (color_type.has_value() && color_type_matches_polar_space(*color_type, polar_color_space)) - return; - } - - bool has_zero_colorfulness = false; - for (size_t i = 0; i < 3; ++i) { - if (target_categories.component(i) == ComponentCategory::Colorfulness && fabsf(components[i]) < achromatic_threshold) - has_zero_colorfulness = true; - } - if (polar_color_space == PolarColorSpace::Hwb - && components[1] + components[2] >= 1.0f - achromatic_threshold) - has_zero_colorfulness = true; - - if (has_zero_colorfulness) { - for (size_t i = 0; i < 3; ++i) { - if (target_categories.component(i) == ComponentCategory::Hue) - interp_missing.component(i) = true; - } - } -} - -static void substitute_missing_components( - Gfx::ColorComponents& from_components, Gfx::ColorComponents& to_components, - MissingComponents const& from_missing, MissingComponents const& to_missing) -{ - for (size_t i = 0; i < 3; ++i) { - if (from_missing.component(i) && !to_missing.component(i)) - from_components[i] = to_components[i]; - else if (to_missing.component(i) && !from_missing.component(i)) - to_components[i] = from_components[i]; - } - - if (from_missing.alpha && !to_missing.alpha) - from_components.set_alpha(to_components.alpha()); - else if (to_missing.alpha && !from_missing.alpha) - to_components.set_alpha(from_components.alpha()); - else if (from_missing.alpha && to_missing.alpha) { - from_components.set_alpha(1.0f); - to_components.set_alpha(1.0f); - } -} - -using ColorInterpolationMethod = ColorInterpolationMethodStyleValue::ColorInterpolationMethod; - -struct PreparedInterpolationColor { - MissingComponents missing_components; - ComponentCategories source_categories; - Optional native_components; - Optional srgb_components; -}; - -static ColorSyntax color_syntax_for_interpolation(StyleValue const& style_value) -{ - if (style_value.is_keyword()) - return ColorSyntax::Legacy; - - auto const& color = style_value.as_color(); - auto color_type = color.color_type(); - if (!color_type.has_value()) - return color.color_syntax(); - switch (*color_type) { - case ColorStyleValue::ColorType::RGB: - case ColorStyleValue::ColorType::HSL: - case ColorStyleValue::ColorType::HWB: - return ColorSyntax::Legacy; - default: - return color.color_syntax(); - } -} - -static ComponentCategories source_categories_for_interpolation(StyleValue const& style_value) -{ - if (style_value.is_color()) { - if (auto color_type = style_value.as_color().color_type(); color_type.has_value()) - return categories_for_color_type(*color_type); - return { ComponentCategory::NotAnalogous, ComponentCategory::NotAnalogous, ComponentCategory::NotAnalogous }; - } - return { ComponentCategory::Red, ComponentCategory::Green, ComponentCategory::Blue }; -} - -static InterpolationPolicy resolve_interpolation_policy( - StyleValue const& from, - StyleValue const& to, - Optional color_interpolation_method) -{ - // https://drafts.csswg.org/css-color-4/#interpolation-space - // If the host syntax does not define what color space interpolation should take place in, it defaults to Oklab. - // However, user agents must handle interpolation between legacy sRGB color formats (hex colors, named colors, - // rgb(), hsl() or hwb() and the equivalent alpha-including forms) in gamma-encoded sRGB space. - auto color_syntax = ColorSyntax::Legacy; - if (color_syntax_for_interpolation(from) == ColorSyntax::Modern - || color_syntax_for_interpolation(to) == ColorSyntax::Modern) { - color_syntax = ColorSyntax::Modern; - } - - // NB: When no explicit method is provided, derive from the color syntax. - // When an explicit method IS provided (e.g. color-mix), always use modern output format. - return { - .use_legacy_output = !color_interpolation_method.has_value() && color_syntax == ColorSyntax::Legacy, - .color_interpolation_method = color_interpolation_method.value_or( - ColorInterpolationMethodStyleValue::default_color_interpolation_method(color_syntax)), - }; -} - -static PreparedInterpolationColor initialize_interpolation_color( - StyleValue const& style_value, - ColorResolutionContext const& color_resolution_context) -{ - return { - .missing_components = extract_missing_components(style_value), - .source_categories = source_categories_for_interpolation(style_value), - .native_components = style_value_to_color_components( - style_value, - color_resolution_context.calculation_resolution_context), - .srgb_components = {}, - }; -} - -static Optional resolve_interpolation_color_to_srgb( - StyleValue const& style_value, - PreparedInterpolationColor& color, - ColorResolutionContext const& color_resolution_context) -{ - if (color.srgb_components.has_value()) - return color.srgb_components; - - if (color.native_components.has_value()) { - color.srgb_components = native_components_to_srgb( - color.native_components.value(), - style_value.as_color().color_type().value()); - return color.srgb_components; - } - - auto resolved = style_value.to_color(color_resolution_context); - if (!resolved.has_value()) - return {}; - - color.srgb_components = Gfx::color_to_srgb(resolved.value()); - return color.srgb_components; -} - -static Optional resolve_interpolation_color_alpha( - StyleValue const& style_value, - PreparedInterpolationColor& color, - ColorResolutionContext const& color_resolution_context) -{ - if (color.native_components.has_value()) - return color.native_components->alpha(); - - auto srgb = resolve_interpolation_color_to_srgb(style_value, color, color_resolution_context); - if (!srgb.has_value()) - return {}; - return srgb->alpha(); -} - -static bool prepare_interpolation_color_for_conversion( - StyleValue const& style_value, - PreparedInterpolationColor& color, - ColorResolutionContext const& color_resolution_context) -{ - auto alpha = resolve_interpolation_color_alpha(style_value, color, color_resolution_context); - if (!alpha.has_value()) - return false; - - // https://drafts.csswg.org/css-color-4/#powerless - // NB: When a color has zero alpha, all color components are powerless and we mark them all as missing. - // However, if the alpha is itself `none`, it resolves to 0 but is not truly zero - it will be substituted - // with the other color's alpha during interpolation. - mark_powerless_for_zero_alpha(color.native_components.has_value(), alpha.value(), color.missing_components); - return true; -} - -static Gfx::ColorComponents convert_interpolation_color_to_rectangular_space( - StyleValue const& style_value, - PreparedInterpolationColor& color, - RectangularColorSpace space, - ColorResolutionContext const& color_resolution_context) -{ - if (color.native_components.has_value() && style_value.is_color() - && color_type_matches_rectangular_space(style_value.as_color().color_type().value(), space)) { - return color.native_components.value(); - } - - auto srgb = resolve_interpolation_color_to_srgb(style_value, color, color_resolution_context); - VERIFY(srgb.has_value()); - return srgb_to_rectangular_color_space(srgb.value(), space); -} - -static Gfx::ColorComponents convert_interpolation_color_to_polar_space( - StyleValue const& style_value, - PreparedInterpolationColor& color, - PolarColorSpace space, - ColorResolutionContext const& color_resolution_context) -{ - if (color.native_components.has_value() && style_value.is_color() - && color_type_matches_polar_space(style_value.as_color().color_type().value(), space)) { - return color.native_components.value(); - } - - auto srgb = resolve_interpolation_color_to_srgb(style_value, color, color_resolution_context); - VERIFY(srgb.has_value()); - return srgb_to_polar_color_space(srgb.value(), space); -} - -static size_t hue_index_for_color_space(PolarColorSpace space) -{ - switch (space) { - case PolarColorSpace::Hsl: - case PolarColorSpace::Hwb: - return 0; - case PolarColorSpace::Lch: - case PolarColorSpace::Oklch: - return 2; - } - VERIFY_NOT_REACHED(); -} - -static InterpolationSpaceState convert_to_interpolation_space( - StyleValue const& from, - PreparedInterpolationColor& from_color, - StyleValue const& to, - PreparedInterpolationColor& to_color, - ColorInterpolationMethod const& color_interpolation_method, - ColorResolutionContext const& color_resolution_context) -{ - InterpolationSpaceState state; - - color_interpolation_method.visit( - [&](RectangularColorSpace space) { - state.rectangular_color_space = space; - state.from_components = convert_interpolation_color_to_rectangular_space( - from, from_color, space, color_resolution_context); - state.to_components = convert_interpolation_color_to_rectangular_space( - to, to_color, space, color_resolution_context); - - auto target_categories = categories_for_rectangular_space(space); - state.from_missing = carry_forward_missing_components( - from_color.missing_components, from_color.source_categories, target_categories); - state.to_missing = carry_forward_missing_components( - to_color.missing_components, to_color.source_categories, target_categories); - }, - [&](ColorInterpolationMethodStyleValue::PolarColorInterpolationMethod const& polar_color_interpolation_method) { - state.is_polar = true; - state.polar_color_space = polar_color_interpolation_method.color_space; - state.hue_interpolation_method = polar_color_interpolation_method.hue_interpolation_method; - state.hue_index = hue_index_for_color_space(polar_color_interpolation_method.color_space); - state.from_components = convert_interpolation_color_to_polar_space( - from, from_color, polar_color_interpolation_method.color_space, color_resolution_context); - state.to_components = convert_interpolation_color_to_polar_space( - to, to_color, polar_color_interpolation_method.color_space, color_resolution_context); - - auto target_categories = categories_for_polar_space(polar_color_interpolation_method.color_space); - state.from_missing = carry_forward_missing_components( - from_color.missing_components, from_color.source_categories, target_categories); - state.to_missing = carry_forward_missing_components( - to_color.missing_components, to_color.source_categories, target_categories); - state.polar_target_categories = target_categories; - }); - - if (state.is_polar) { - mark_powerless_hue_after_conversion( - state.from_components, state.from_missing, from, state.polar_color_space, state.polar_target_categories); - mark_powerless_hue_after_conversion( - state.to_components, state.to_missing, to, state.polar_color_space, state.polar_target_categories); - } - - return state; -} - -static bool reinsert_carried_forward_values(InterpolationSpaceState& state) -{ - bool both_alpha_missing = state.from_missing.alpha && state.to_missing.alpha; - substitute_missing_components(state.from_components, state.to_components, state.from_missing, state.to_missing); - return both_alpha_missing; -} - -static void fixup_hues_if_required(InterpolationSpaceState& state) -{ - if (!state.is_polar) - return; - fixup_hue(state.from_components[state.hue_index], state.to_components[state.hue_index], state.hue_interpolation_method); -} - -static Gfx::ColorComponents premultiply_color_components( - Gfx::ColorComponents const& components, - bool is_polar, - size_t hue_index) -{ - Gfx::ColorComponents premultiplied; - premultiplied.set_alpha(components.alpha()); - - for (size_t i = 0; i < 3; ++i) { - if (is_polar && i == hue_index) - premultiplied[i] = components[i]; - else - premultiplied[i] = components[i] * components.alpha(); - } - - return premultiplied; -} - -static Gfx::ColorComponents interpolate_premultiplied_components( - Gfx::ColorComponents const& from, - Gfx::ColorComponents const& to, - float delta) -{ - Gfx::ColorComponents interpolated; - for (size_t i = 0; i < 3; ++i) - interpolated[i] = interpolate_color_component(from[i], to[i], delta); - return interpolated; -} - -static Gfx::ColorComponents unpremultiply_color_components( - Gfx::ColorComponents const& premultiplied, - float interpolated_alpha, - bool is_polar, - size_t hue_index) -{ - Gfx::ColorComponents result; - result.set_alpha(interpolated_alpha); - - for (size_t i = 0; i < 3; ++i) { - bool was_premultiplied = !is_polar || i != hue_index; - result[i] = was_premultiplied ? premultiplied[i] / interpolated_alpha : premultiplied[i]; - } - - return result; -} - -static MissingComponents result_missing_components(InterpolationSpaceState const& state) -{ - // https://drafts.csswg.org/css-color-4/#interpolation-missing - // NB: If both input colors have a component as missing, the result also has that component as missing. - return { - state.from_missing.component(0) && state.to_missing.component(0), - state.from_missing.component(1) && state.to_missing.component(1), - state.from_missing.component(2) && state.to_missing.component(2), - state.from_missing.alpha && state.to_missing.alpha, - }; -} - -RefPtr style_value_for_interpolated_color(InterpolatedColor const& interpolated) -{ - // https://drafts.csswg.org/css-color-4/#interpolation-space - // NB: Legacy sRGB content interpolates in sRGB and produces a legacy rgb() result. - if (interpolated.policy.use_legacy_output) - return ColorStyleValue::create_from_color(Gfx::srgb_to_color(interpolated.components), ColorSyntax::Legacy); - - // NB: Return as a StyleValue in the interpolation color space. - if (interpolated.state.is_polar) - return style_value_from_polar_color_space(interpolated.components, interpolated.state.polar_color_space, interpolated.missing); - return style_value_from_rectangular_color_space(interpolated.components, interpolated.state.rectangular_color_space, interpolated.missing); -} - -// https://drafts.csswg.org/css-color-4/#interpolation -Optional perform_color_interpolation( - StyleValue const& from, StyleValue const& to, float delta, - Optional color_interpolation_method, - ColorResolutionContext const& color_resolution_context) -{ - // 1. checking the two colors for analogous components and analogous sets which will be carried forward - auto from_color = initialize_interpolation_color(from, color_resolution_context); - auto to_color = initialize_interpolation_color(to, color_resolution_context); - - // 2. prepare both colors for conversion. this changes any powerless components to missing values - if (!prepare_interpolation_color_for_conversion(from, from_color, color_resolution_context) - || !prepare_interpolation_color_for_conversion(to, to_color, color_resolution_context)) { - return {}; - } - - // 3. converting them both to a given color space which will be referred to as the interpolation color space - // below. - auto interpolation_policy = resolve_interpolation_policy(from, to, color_interpolation_method); - auto state = convert_to_interpolation_space( - from, from_color, to, to_color, interpolation_policy.color_interpolation_method, color_resolution_context); - - // 4. (if required) re-inserting carried forward values in the converted colors - auto both_alpha_missing = reinsert_carried_forward_values(state); - - // 5. (if required) fixing up the hues, depending on the selected - fixup_hues_if_required(state); - - auto interpolated_alpha = interpolate_color_component(state.from_components.alpha(), state.to_components.alpha(), delta); - auto clamped_alpha = clamp(interpolated_alpha, 0.0f, 1.0f); - if (clamped_alpha == 0.0f && !both_alpha_missing) { - // OPTIMIZATION: Fully transparent results can skip the premultiply/interpolate/unpremultiply cycle. - Gfx::ColorComponents zero_result { 0.0f, 0.0f, 0.0f, 0.0f }; - return InterpolatedColor { zero_result, {}, interpolation_policy, move(state) }; - } - - // 6. changing the color components to premultiplied form - // https://drafts.csswg.org/css-color-4/#interpolation-alpha - // For rectangular orthogonal color coordinate systems, all component values are multiplied by the alpha value. - // For cylindrical polar color coordinate systems, the hue angle is NOT premultiplied. - auto from_premultiplied = premultiply_color_components(state.from_components, state.is_polar, state.hue_index); - auto to_premultiplied = premultiply_color_components(state.to_components, state.is_polar, state.hue_index); - - // 7. linearly interpolating each component of the computed value of the color separately - auto interpolated_components = interpolate_premultiplied_components(from_premultiplied, to_premultiplied, delta); - - // 8. undoing premultiplication - auto result = unpremultiply_color_components(interpolated_components, clamped_alpha, state.is_polar, state.hue_index); - - auto missing = result_missing_components(state); - return InterpolatedColor { result, missing, interpolation_policy, move(state) }; -} - -// https://drafts.csswg.org/css-color-4/#interpolation -RefPtr interpolate_color( - StyleValue const& from, StyleValue const& to, float delta, - Optional color_interpolation_method, - ColorResolutionContext const& color_resolution_context) -{ - auto interpolated = perform_color_interpolation(from, to, delta, color_interpolation_method, color_resolution_context); - if (!interpolated.has_value()) - return {}; - return style_value_for_interpolated_color(*interpolated); -} - -} diff --git a/Libraries/LibWeb/CSS/ColorInterpolation.h b/Libraries/LibWeb/CSS/ColorInterpolation.h deleted file mode 100644 index 7af119a3ae6c8..0000000000000 --- a/Libraries/LibWeb/CSS/ColorInterpolation.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2026, Tim Ledbetter - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace Web::CSS { - -struct MissingComponents { - Array components { false, false, false }; - bool alpha { false }; - - constexpr MissingComponents() = default; - constexpr MissingComponents(bool first, bool second, bool third, bool alpha_value = false) - : components { first, second, third } - , alpha(alpha_value) - { - } - - bool& component(size_t index) { return components[index]; } - bool component(size_t index) const { return components[index]; } -}; - -enum class ComponentCategory : u8 { - Red, - Green, - Blue, - Lightness, - Colorfulness, - Hue, - OpponentA, - OpponentB, - NotAnalogous, -}; - -struct ComponentCategories { - Array components { ComponentCategory::NotAnalogous, ComponentCategory::NotAnalogous, ComponentCategory::NotAnalogous }; - - constexpr ComponentCategories() = default; - constexpr ComponentCategories(ComponentCategory first, ComponentCategory second, ComponentCategory third) - : components { first, second, third } - { - } - - ComponentCategory component(size_t index) const { return components[index]; } - bool operator==(ComponentCategories const&) const = default; -}; - -struct InterpolationPolicy { - bool use_legacy_output; - ColorInterpolationMethodStyleValue::ColorInterpolationMethod color_interpolation_method; -}; - -struct InterpolationSpaceState { - Gfx::ColorComponents from_components; - Gfx::ColorComponents to_components; - MissingComponents from_missing; - MissingComponents to_missing; - size_t hue_index { 0 }; - HueInterpolationMethod hue_interpolation_method { HueInterpolationMethod::Shorter }; - PolarColorSpace polar_color_space { PolarColorSpace::Hsl }; - RectangularColorSpace rectangular_color_space { RectangularColorSpace::Srgb }; - bool is_polar { false }; - ComponentCategories polar_target_categories {}; -}; - -struct InterpolatedColor { - Gfx::ColorComponents components; - MissingComponents missing; - InterpolationPolicy policy; - InterpolationSpaceState state; -}; - -Optional perform_color_interpolation( - StyleValue const& from, StyleValue const& to, float delta, - Optional color_interpolation_method, - ColorResolutionContext const& color_resolution_context); - -RefPtr style_value_for_interpolated_color(InterpolatedColor const&); - -} diff --git a/Libraries/LibWeb/CSS/ComputedProperties.cpp b/Libraries/LibWeb/CSS/ComputedProperties.cpp index ee96ca2fc1f88..f3621c92cf397 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.cpp +++ b/Libraries/LibWeb/CSS/ComputedProperties.cpp @@ -92,13 +92,41 @@ RefPtr ComputedValues::animated_properties_snapshot() return m_animated_properties; } +RefPtr ComputedValues::style_value_from_handle(PropertyID property_id, RustStyleValueHandle const& handle) const +{ + if (!handle) { + m_style_value_cache.remove(property_id); + return nullptr; + } + if (auto it = m_style_value_cache.find(property_id); it != m_style_value_cache.end() && it->value->rust_style_value_data() == handle.data()) + return it->value; + auto value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(handle.data())); + m_style_value_cache.set(property_id, value); + return value; +} + RefPtr ComputedValues::color_style_value() const { if (m_inherited.text->color_style_value) - return m_inherited.text->color_style_value; + return style_value_from_handle(PropertyID::Color, m_inherited.text->color_style_value); return computed_style_value(PropertyID::Color); } +RefPtr ComputedValues::word_spacing_style_value() const +{ + return style_value_from_handle(PropertyID::WordSpacing, m_inherited.text->word_spacing_style_value); +} + +RefPtr ComputedValues::letter_spacing_style_value() const +{ + return style_value_from_handle(PropertyID::LetterSpacing, m_inherited.text->letter_spacing_style_value); +} + +RefPtr ComputedValues::background_color_style_value() const +{ + return style_value_from_handle(PropertyID::BackgroundColor, m_noninherited.background->background_color_style_value); +} + static_assert(to_underlying(PseudoElement::KnownPseudoElementCount) <= sizeof(u64) * 8); RefPtr ComputedValues::computed_style_value(PropertyID property_id, WithAnimationsApplied with_animations_applied) const @@ -449,20 +477,20 @@ RefPtr ComputedValues::computed_style_value(PropertyID propert { length_style_value(border_spacing_horizontal()), length_style_value(border_spacing_vertical()) }, StyleValueList::Separator::Space); case PropertyID::BorderBottomColor: - if (m_noninherited.border->border_bottom_color_style_value && !m_noninherited.border->border_bottom_color_style_value->depends_on_current_color()) - return m_noninherited.border->border_bottom_color_style_value; + if (auto value = style_value_from_handle(PropertyID::BorderBottomColor, m_noninherited.border->border_bottom_color_style_value); value && !value->depends_on_current_color()) + return value; return color_style_value(border_bottom().color); case PropertyID::BorderLeftColor: - if (m_noninherited.border->border_left_color_style_value && !m_noninherited.border->border_left_color_style_value->depends_on_current_color()) - return m_noninherited.border->border_left_color_style_value; + if (auto value = style_value_from_handle(PropertyID::BorderLeftColor, m_noninherited.border->border_left_color_style_value); value && !value->depends_on_current_color()) + return value; return color_style_value(border_left().color); case PropertyID::BorderRightColor: - if (m_noninherited.border->border_right_color_style_value && !m_noninherited.border->border_right_color_style_value->depends_on_current_color()) - return m_noninherited.border->border_right_color_style_value; + if (auto value = style_value_from_handle(PropertyID::BorderRightColor, m_noninherited.border->border_right_color_style_value); value && !value->depends_on_current_color()) + return value; return color_style_value(border_right().color); case PropertyID::BorderTopColor: - if (m_noninherited.border->border_top_color_style_value && !m_noninherited.border->border_top_color_style_value->depends_on_current_color()) - return m_noninherited.border->border_top_color_style_value; + if (auto value = style_value_from_handle(PropertyID::BorderTopColor, m_noninherited.border->border_top_color_style_value); value && !value->depends_on_current_color()) + return value; return color_style_value(border_top().color); case PropertyID::CaretColor: return color_or_auto_style_value(caret_color_value()); @@ -481,8 +509,8 @@ RefPtr ComputedValues::computed_style_value(PropertyID propert case PropertyID::ColumnWidth: return size_style_value(column_width()); case PropertyID::Color: - if (m_inherited.text->color_style_value && !m_inherited.text->color_style_value->depends_on_current_color()) - return m_inherited.text->color_style_value; + if (auto value = style_value_from_handle(PropertyID::Color, m_inherited.text->color_style_value); value && !value->depends_on_current_color()) + return value; return color_style_value(color()); case PropertyID::FloodColor: return color_style_value(flood_color()); @@ -1929,18 +1957,6 @@ void ComputedProperties::Builder::set_property_without_modifying_flags(PropertyI style().clear_computed_font_list_cache(); } -void ComputedProperties::Builder::revert_property(PropertyID id, ComputedProperties const& style_for_revert) -{ - VERIFY(id >= first_longhand_property_id && id <= last_longhand_property_id); - - data().property_values[to_underlying(id) - to_underlying(first_longhand_property_id)] = style_for_revert.data().property_values[to_underlying(id) - to_underlying(first_longhand_property_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); - - if (property_affects_computed_font_list(id)) - style().clear_computed_font_list_cache(); -} - Display ComputedProperties::display_before_box_type_transformation() const { return data().display_before_box_type_transformation; diff --git a/Libraries/LibWeb/CSS/ComputedProperties.h b/Libraries/LibWeb/CSS/ComputedProperties.h index 7ed8c3b77ad34..d4959a3029d01 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.h +++ b/Libraries/LibWeb/CSS/ComputedProperties.h @@ -103,8 +103,6 @@ class ComputedProperties final : public RefCounted { void set_property(PropertyID, NonnullRefPtr value, Inherited = Inherited::No, Important = Important::No); void set_property_without_modifying_flags(PropertyID, NonnullRefPtr value); - void revert_property(PropertyID, ComputedProperties const& style_for_revert); - void set_display_before_box_type_transformation(Display); bool has_effective_color_scheme() const { return m_data->effective_color_scheme.has_value(); } diff --git a/Libraries/LibWeb/CSS/ComputedValues.cpp b/Libraries/LibWeb/CSS/ComputedValues.cpp index e9ff846ebc3db..e0edbecac57eb 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.cpp +++ b/Libraries/LibWeb/CSS/ComputedValues.cpp @@ -46,7 +46,19 @@ namespace Web::CSS { template static consteval ComputedValuesFFI::StyleGroupVTable make_style_group_vtable() { + if constexpr (requires { T::style_group_lifecycle; }) { + return { + .lifecycle = T::style_group_lifecycle, + .size = sizeof(T), + .align = alignof(T), + .default_construct = nullptr, + .copy_construct = nullptr, + .destruct = nullptr, + .equals = nullptr, + }; + } return { + .lifecycle = ComputedValuesFFI::StyleGroupLifecycle::Cpp, .size = sizeof(T), .align = alignof(T), .default_construct = [](void* payload) { @@ -82,25 +94,6 @@ static Array const& keyword_code_table() return table; } -// The properties feeding the alignment group's descriptors, in registration -// order; create() gathers their computed values in the same order. -static constexpr Array alignment_group_properties { - PropertyID::FlexDirection, - PropertyID::FlexWrap, - PropertyID::FlexBasis, - PropertyID::FlexGrow, - PropertyID::FlexShrink, - PropertyID::Order, - PropertyID::AlignContent, - PropertyID::AlignItems, - PropertyID::AlignSelf, - PropertyID::JustifyContent, - PropertyID::JustifyItems, - PropertyID::JustifySelf, - PropertyID::ColumnGap, - PropertyID::RowGap, -}; - // The properties feeding the text reset group's descriptors, in registration // order. static constexpr Array text_reset_group_properties { @@ -250,19 +243,6 @@ static constexpr Array inherited_ui_group_properties { PropertyID::ColorScheme, }; -// The properties feeding the sizing group's descriptors, in registration -// order. All six register as keyword constraints: the group adopts a shared -// payload when every size is untouched and falls back to the setters -// otherwise, until the core learns the size representation. -static constexpr Array sizing_group_properties { - PropertyID::Width, - PropertyID::MinWidth, - PropertyID::MaxWidth, - PropertyID::Height, - PropertyID::MinHeight, - PropertyID::MaxHeight, -}; - // The properties feeding the transform group's descriptors, in registration // order. static constexpr Array transform_group_properties { @@ -335,24 +315,6 @@ static constexpr Array animation_group_properties { PropertyID::TransitionBehavior, }; -// The properties feeding the SVG reset group's descriptors, in registration -// order. -static constexpr Array svg_reset_group_properties { - PropertyID::Cx, - PropertyID::Cy, - PropertyID::R, - PropertyID::Rx, - PropertyID::Ry, - PropertyID::X, - PropertyID::Y, - PropertyID::StopColor, - PropertyID::StopOpacity, - PropertyID::FloodColor, - PropertyID::FloodOpacity, - PropertyID::VectorEffect, - PropertyID::ShapeRendering, -}; - // The properties feeding the inherited SVG group's descriptors, in // registration order. static constexpr Array inherited_svg_group_properties { @@ -424,24 +386,6 @@ static constexpr Array box_group_properties { PropertyID::Resize, }; -// The properties feeding the surround group's descriptors, in registration -// order. Everything registers as constraints until the core learns the -// length box representation. -static constexpr Array surround_group_properties { - PropertyID::Top, - PropertyID::Right, - PropertyID::Bottom, - PropertyID::Left, - PropertyID::MarginTop, - PropertyID::MarginRight, - PropertyID::MarginBottom, - PropertyID::MarginLeft, - PropertyID::PaddingTop, - PropertyID::PaddingRight, - PropertyID::PaddingBottom, - PropertyID::PaddingLeft, -}; - // The properties feeding the border group's descriptors, in registration // order; each side's color feeds both the resolved color and the retained // shell. @@ -514,23 +458,6 @@ static void register_style_group_field_descriptors() }); }; - using Alignment = ComputedValues::AlignmentValues; - constexpr auto alignment = to_underlying(StyleGroupIndex::AlignmentValues); - add(alignment, PropertyID::FlexDirection, offsetof(Alignment, flex_direction), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(alignment, PropertyID::FlexWrap, offsetof(Alignment, flex_wrap), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(alignment, PropertyID::FlexBasis, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); - add(alignment, PropertyID::FlexGrow, offsetof(Alignment, flex_grow), GROUP_FIELD_F64, 0, nullptr); - add(alignment, PropertyID::FlexShrink, offsetof(Alignment, flex_shrink), GROUP_FIELD_F64, 0, nullptr); - add(alignment, PropertyID::Order, offsetof(Alignment, order), GROUP_FIELD_I32, 0, nullptr); - add(alignment, PropertyID::AlignContent, offsetof(Alignment, align_content), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(alignment, PropertyID::AlignItems, offsetof(Alignment, align_items), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(alignment, PropertyID::AlignSelf, offsetof(Alignment, align_self), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(alignment, PropertyID::JustifyContent, offsetof(Alignment, justify_content), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(alignment, PropertyID::JustifyItems, offsetof(Alignment, justify_items), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(alignment, PropertyID::JustifySelf, offsetof(Alignment, justify_self), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(alignment, PropertyID::ColumnGap, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Normal), nullptr); - add(alignment, PropertyID::RowGap, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Normal), nullptr); - static_assert(sizeof(Color) == sizeof(u32)); using TextReset = ComputedValues::TextResetValues; constexpr auto text_reset = to_underlying(StyleGroupIndex::TextResetValues); @@ -586,7 +513,7 @@ static void register_style_group_field_descriptors() using InheritedText = ComputedValues::InheritedTextValues; constexpr auto inherited_text = to_underlying(StyleGroupIndex::InheritedTextValues); add(inherited_text, PropertyID::Color, offsetof(InheritedText, color), GROUP_FIELD_COLOR, 0, nullptr); - add(inherited_text, PropertyID::Color, offsetof(InheritedText, color_style_value), GROUP_FIELD_RETAINED_SHELL, 0, nullptr); + add(inherited_text, PropertyID::Color, offsetof(InheritedText, color_style_value), GROUP_FIELD_RETAINED_DATA, 0, nullptr); add(inherited_text, PropertyID::WebkitTextFillColor, offsetof(InheritedText, webkit_text_fill_color), GROUP_FIELD_COLOR, 0, nullptr); add(inherited_text, PropertyID::WebkitTextFillColor, offsetof(InheritedText, webkit_text_fill_color_is_current_color), GROUP_FIELD_KEYWORD_EQUALS_BOOL, to_underlying(Keyword::Currentcolor), nullptr); add(inherited_text, PropertyID::TextShadow, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::None), nullptr); @@ -604,9 +531,9 @@ static void register_style_group_field_descriptors() add(inherited_text, PropertyID::WordBreak, offsetof(InheritedText, word_break), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); add(inherited_text, PropertyID::OverflowWrap, offsetof(InheritedText, overflow_wrap), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); add(inherited_text, PropertyID::WordSpacing, offsetof(InheritedText, word_spacing), GROUP_FIELD_CSS_PIXELS, 0, nullptr); - add(inherited_text, PropertyID::WordSpacing, offsetof(InheritedText, word_spacing_style_value), GROUP_FIELD_RETAINED_SHELL, 0, nullptr); + add(inherited_text, PropertyID::WordSpacing, offsetof(InheritedText, word_spacing_style_value), GROUP_FIELD_RETAINED_DATA, 0, nullptr); add(inherited_text, PropertyID::LetterSpacing, offsetof(InheritedText, letter_spacing), GROUP_FIELD_CSS_PIXELS, 0, nullptr); - add(inherited_text, PropertyID::LetterSpacing, offsetof(InheritedText, letter_spacing_style_value), GROUP_FIELD_RETAINED_SHELL, 0, nullptr); + add(inherited_text, PropertyID::LetterSpacing, offsetof(InheritedText, letter_spacing_style_value), GROUP_FIELD_RETAINED_DATA, 0, nullptr); add(inherited_text, PropertyID::Orphans, offsetof(InheritedText, orphans), GROUP_FIELD_U64, 0, nullptr); add(inherited_text, PropertyID::Widows, offsetof(InheritedText, widows), GROUP_FIELD_U64, 0, nullptr); @@ -622,14 +549,6 @@ static void register_style_group_field_descriptors() add(inherited_ui, PropertyID::ColorScheme, offsetof(InheritedUI, color_scheme), GROUP_FIELD_RESOLVED_U8, 0, nullptr); add(inherited_ui, PropertyID::ColorScheme, 0, GROUP_FIELD_REQUIRE_INITIAL_VALUE, 0, nullptr); - constexpr auto sizing = to_underlying(StyleGroupIndex::SizingValues); - add(sizing, PropertyID::Width, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); - add(sizing, PropertyID::MinWidth, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); - add(sizing, PropertyID::MaxWidth, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::None), nullptr); - add(sizing, PropertyID::Height, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); - add(sizing, PropertyID::MinHeight, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); - add(sizing, PropertyID::MaxHeight, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::None), nullptr); - using Transform = ComputedValues::TransformValues; constexpr auto transform = to_underlying(StyleGroupIndex::TransformValues); add(transform, PropertyID::Transform, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::None), nullptr); @@ -663,19 +582,6 @@ static void register_style_group_field_descriptors() for (auto property : animation_group_properties) add(animation, property, 0, GROUP_FIELD_REQUIRE_INITIAL_VALUE, 0, nullptr); - using SVGReset = ComputedValues::SVGResetValues; - constexpr auto svg_reset = to_underlying(StyleGroupIndex::SVGResetValues); - for (auto property : { PropertyID::Cx, PropertyID::Cy, PropertyID::R, PropertyID::X, PropertyID::Y }) - add(svg_reset, property, 0, GROUP_FIELD_REQUIRE_INITIAL_VALUE, 0, nullptr); - add(svg_reset, PropertyID::Rx, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); - add(svg_reset, PropertyID::Ry, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); - add(svg_reset, PropertyID::StopColor, offsetof(SVGReset, stop_color), GROUP_FIELD_COLOR, 0, nullptr); - add(svg_reset, PropertyID::StopOpacity, offsetof(SVGReset, stop_opacity), GROUP_FIELD_RESOLVED_F32, 0, nullptr); - add(svg_reset, PropertyID::FloodColor, offsetof(SVGReset, flood_color), GROUP_FIELD_COLOR, 0, nullptr); - add(svg_reset, PropertyID::FloodOpacity, offsetof(SVGReset, flood_opacity), GROUP_FIELD_RESOLVED_F32, 0, nullptr); - add(svg_reset, PropertyID::VectorEffect, offsetof(SVGReset, vector_effect), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - add(svg_reset, PropertyID::ShapeRendering, offsetof(SVGReset, shape_rendering), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - using InheritedSVG = ComputedValues::InheritedSVGValues; constexpr auto inherited_svg = to_underlying(StyleGroupIndex::InheritedSVGValues); add(inherited_svg, PropertyID::Fill, 0, GROUP_FIELD_REQUIRE_INITIAL_VALUE, 0, nullptr); @@ -738,13 +644,6 @@ static void register_style_group_field_descriptors() add(box, PropertyID::WillChange, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); add(box, PropertyID::Resize, offsetof(Box, resize), GROUP_FIELD_ENUM_KEYWORD, 0, &keyword_code_table()); - constexpr auto surround = to_underlying(StyleGroupIndex::SurroundValues); - for (auto property : { PropertyID::Top, PropertyID::Right, PropertyID::Bottom, PropertyID::Left }) - add(surround, property, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::Auto), nullptr); - for (auto property : { PropertyID::MarginTop, PropertyID::MarginRight, PropertyID::MarginBottom, PropertyID::MarginLeft, - PropertyID::PaddingTop, PropertyID::PaddingRight, PropertyID::PaddingBottom, PropertyID::PaddingLeft }) - add(surround, property, 0, GROUP_FIELD_REQUIRE_PX, 0, nullptr, 0); - using Border = ComputedValues::BorderValues; constexpr auto border = to_underlying(StyleGroupIndex::BorderValues); struct BorderSide { @@ -752,7 +651,7 @@ static void register_style_group_field_descriptors() PropertyID style; PropertyID width; u32 data_offset; - u32 shell_offset; + u32 data_handle_offset; u32 computed_width_offset; }; for (auto const& side : { @@ -762,7 +661,7 @@ static void register_style_group_field_descriptors() BorderSide { PropertyID::BorderBottomColor, PropertyID::BorderBottomStyle, PropertyID::BorderBottomWidth, offsetof(Border, border_bottom), offsetof(Border, border_bottom_color_style_value), offsetof(Border, border_bottom_computed_width) }, }) { add(border, side.color, side.data_offset + offsetof(BorderData, color), GROUP_FIELD_COLOR, 0, nullptr); - add(border, side.color, side.shell_offset, GROUP_FIELD_RETAINED_SHELL, 0, nullptr); + add(border, side.color, side.data_handle_offset, GROUP_FIELD_RETAINED_DATA, 0, nullptr); // NB: A none border-style keeps BorderData's width at the constructor's zero, // matching the used-width rule; styled borders take the C++ path. add(border, side.style, 0, GROUP_FIELD_REQUIRE_KEYWORD, to_underlying(Keyword::None), nullptr); @@ -783,7 +682,7 @@ static void register_style_group_field_descriptors() using Background = ComputedValues::BackgroundValues; constexpr auto background = to_underlying(StyleGroupIndex::BackgroundValues); add(background, PropertyID::BackgroundColor, offsetof(Background, background_color), GROUP_FIELD_COLOR, 0, nullptr); - add(background, PropertyID::BackgroundColor, offsetof(Background, background_color_style_value), GROUP_FIELD_RETAINED_SHELL, 0, nullptr); + add(background, PropertyID::BackgroundColor, offsetof(Background, background_color_style_value), GROUP_FIELD_RETAINED_DATA, 0, nullptr); for (auto property : { PropertyID::BackgroundImage, PropertyID::BackgroundClip, PropertyID::BackgroundAttachment, PropertyID::BackgroundOrigin, PropertyID::BackgroundPositionX, PropertyID::BackgroundPositionY, PropertyID::BackgroundRepeat, PropertyID::BackgroundSize, PropertyID::BackgroundBlendMode }) @@ -798,6 +697,18 @@ static_assert(sizeof(ComputedValues::InheritedBoxValues) == sizeof(ComputedValue static_assert(alignof(ComputedValues::InheritedBoxValues) == alignof(ComputedValuesFFI::InheritedBoxValues)); static_assert(sizeof(ComputedValues::InheritedTableValues) == sizeof(ComputedValuesFFI::InheritedTableValues)); static_assert(alignof(ComputedValues::InheritedTableValues) == alignof(ComputedValuesFFI::InheritedTableValues)); +static_assert(sizeof(ComputedValues::SizingValues) == sizeof(ComputedValuesFFI::SizingValues)); +static_assert(alignof(ComputedValues::SizingValues) == alignof(ComputedValuesFFI::SizingValues)); +static_assert(sizeof(ComputedValues::AlignmentValues) == sizeof(ComputedValuesFFI::AlignmentValues)); +static_assert(alignof(ComputedValues::AlignmentValues) == alignof(ComputedValuesFFI::AlignmentValues)); +static_assert(sizeof(ComputedValues::SVGResetValues) == sizeof(ComputedValuesFFI::SVGResetValues)); +static_assert(alignof(ComputedValues::SVGResetValues) == alignof(ComputedValuesFFI::SVGResetValues)); +static_assert(sizeof(ComputedValues::SurroundValues) == sizeof(ComputedValuesFFI::SurroundValues)); +static_assert(alignof(ComputedValues::SurroundValues) == alignof(ComputedValuesFFI::SurroundValues)); +static_assert(sizeof(Size) == sizeof(ComputedValuesFFI::ComputedSize)); +static_assert(alignof(Size) == alignof(ComputedValuesFFI::ComputedSize)); +static_assert(sizeof(RustStyleValueHandle) == sizeof(StyleValueFFI::StyleValueData const*)); +static_assert(alignof(RustStyleValueHandle) == alignof(StyleValueFFI::StyleValueData const*)); void const* style_group_default_payload(size_t group_index) { @@ -981,7 +892,7 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co auto gather_group_values = [&](Array const& properties, Array& entries) { for (size_t i = 0; i < N; ++i) { auto const& value = computed_style.property(properties[i]); - entries[i] = { &value, value.rust_style_value_data(), 0, false, 0, false }; + entries[i] = { value.rust_style_value_data(), 0, false, 0, false }; } }; @@ -1007,16 +918,23 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co if (anchor_adopted) computed_values.adopt_anchor_group(const_cast(anchor_payload)); - Array surround_group_values; - gather_group_values(surround_group_properties, surround_group_values); - auto* surround_payload = ComputedValuesFFI::rust_build_style_group( + auto* surround_payload = ComputedValuesFFI::rust_build_surround_group( SurroundValues::style_group_index, - surround_group_values.data(), - surround_group_values.size(), + computed_style.property(PropertyID::Top).rust_style_value_data(), + computed_style.property(PropertyID::Right).rust_style_value_data(), + computed_style.property(PropertyID::Bottom).rust_style_value_data(), + computed_style.property(PropertyID::Left).rust_style_value_data(), + computed_style.property(PropertyID::MarginTop).rust_style_value_data(), + computed_style.property(PropertyID::MarginRight).rust_style_value_data(), + computed_style.property(PropertyID::MarginBottom).rust_style_value_data(), + computed_style.property(PropertyID::MarginLeft).rust_style_value_data(), + computed_style.property(PropertyID::PaddingTop).rust_style_value_data(), + computed_style.property(PropertyID::PaddingRight).rust_style_value_data(), + computed_style.property(PropertyID::PaddingBottom).rust_style_value_data(), + computed_style.property(PropertyID::PaddingLeft).rust_style_value_data(), inherit_parent ? static_cast(inherit_parent->m_noninherited.surround.operator->()) : nullptr); - bool const surround_adopted = surround_payload != nullptr; - if (surround_adopted) - computed_values.adopt_surround_group(const_cast(surround_payload)); + VERIFY(surround_payload); + computed_values.adopt_surround_group(const_cast(surround_payload)); Array box_group_values; gather_group_values(box_group_properties, box_group_values); @@ -1029,27 +947,36 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co if (box_adopted) computed_values.adopt_box_group(const_cast(box_payload)); - Array alignment_group_values; - gather_group_values(alignment_group_properties, alignment_group_values); - auto* alignment_payload = ComputedValuesFFI::rust_build_style_group( + auto* alignment_payload = ComputedValuesFFI::rust_build_alignment_group( AlignmentValues::style_group_index, - alignment_group_values.data(), - alignment_group_values.size(), + computed_style.property(PropertyID::FlexDirection).rust_style_value_data(), + computed_style.property(PropertyID::FlexWrap).rust_style_value_data(), + computed_style.property(PropertyID::FlexBasis).rust_style_value_data(), + computed_style.flex_grow(), + computed_style.flex_shrink(), + computed_style.order(), + computed_style.property(PropertyID::AlignContent).rust_style_value_data(), + computed_style.property(PropertyID::AlignItems).rust_style_value_data(), + computed_style.property(PropertyID::AlignSelf).rust_style_value_data(), + computed_style.property(PropertyID::JustifyContent).rust_style_value_data(), + computed_style.property(PropertyID::JustifyItems).rust_style_value_data(), + computed_style.property(PropertyID::JustifySelf).rust_style_value_data(), + computed_style.property(PropertyID::ColumnGap).rust_style_value_data(), + computed_style.property(PropertyID::RowGap).rust_style_value_data(), inherit_parent ? static_cast(inherit_parent->m_noninherited.alignment.operator->()) : nullptr); - bool const alignment_adopted = alignment_payload != nullptr; - if (alignment_adopted) - computed_values.adopt_alignment_group(const_cast(alignment_payload)); + VERIFY(alignment_payload); + computed_values.adopt_alignment_group(const_cast(alignment_payload)); - Array sizing_group_values; - gather_group_values(sizing_group_properties, sizing_group_values); - auto* sizing_payload = ComputedValuesFFI::rust_build_style_group( + auto* sizing_payload = ComputedValuesFFI::rust_build_sizing_group( SizingValues::style_group_index, - sizing_group_values.data(), - sizing_group_values.size(), + computed_style.property(PropertyID::Width).rust_style_value_data(), + computed_style.property(PropertyID::MinWidth).rust_style_value_data(), + computed_style.property(PropertyID::MaxWidth).rust_style_value_data(), + computed_style.property(PropertyID::Height).rust_style_value_data(), + computed_style.property(PropertyID::MinHeight).rust_style_value_data(), + computed_style.property(PropertyID::MaxHeight).rust_style_value_data(), inherit_parent ? static_cast(inherit_parent->m_noninherited.sizing.operator->()) : nullptr); - bool const sizing_adopted = sizing_payload != nullptr; - if (sizing_adopted) - computed_values.adopt_sizing_group(const_cast(sizing_payload)); + computed_values.adopt_sizing_group(const_cast(sizing_payload)); Array grid_group_values; gather_group_values(grid_group_properties, grid_group_values); @@ -1350,29 +1277,24 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co if (inherited_svg_adopted) computed_values.adopt_inherited_svg_group(const_cast(inherited_svg_payload)); - Array svg_reset_group_values; - gather_group_values(svg_reset_group_properties, svg_reset_group_values); - for (size_t i = 0; i < svg_reset_group_properties.size(); ++i) { - auto svg_property_id = svg_reset_group_properties[i]; - if (svg_property_id == PropertyID::StopColor || svg_property_id == PropertyID::FloodColor) { - svg_reset_group_values[i].resolved_color = computed_style.color(svg_property_id, own_color_resolution_context).value(); - svg_reset_group_values[i].has_resolved_color = true; - } else if (svg_property_id == PropertyID::StopOpacity) { - svg_reset_group_values[i].resolved_number = computed_style.stop_opacity(); - svg_reset_group_values[i].has_resolved_number = true; - } else if (svg_property_id == PropertyID::FloodOpacity) { - svg_reset_group_values[i].resolved_number = computed_style.flood_opacity(); - svg_reset_group_values[i].has_resolved_number = true; - } - } - auto* svg_reset_payload = ComputedValuesFFI::rust_build_style_group( + auto* svg_reset_payload = ComputedValuesFFI::rust_build_svg_reset_group( SVGResetValues::style_group_index, - svg_reset_group_values.data(), - svg_reset_group_values.size(), + computed_style.property(PropertyID::Cx).rust_style_value_data(), + computed_style.property(PropertyID::Cy).rust_style_value_data(), + computed_style.property(PropertyID::R).rust_style_value_data(), + computed_style.property(PropertyID::Rx).rust_style_value_data(), + computed_style.property(PropertyID::Ry).rust_style_value_data(), + computed_style.property(PropertyID::X).rust_style_value_data(), + computed_style.property(PropertyID::Y).rust_style_value_data(), + computed_style.color(PropertyID::StopColor, own_color_resolution_context).value(), + computed_style.stop_opacity(), + computed_style.color(PropertyID::FloodColor, own_color_resolution_context).value(), + computed_style.flood_opacity(), + computed_style.property(PropertyID::VectorEffect).rust_style_value_data(), + computed_style.property(PropertyID::ShapeRendering).rust_style_value_data(), inherit_parent ? static_cast(inherit_parent->m_noninherited.svg_reset.operator->()) : nullptr); - bool const svg_reset_adopted = svg_reset_payload != nullptr; - if (svg_reset_adopted) - computed_values.adopt_svg_reset_group(const_cast(svg_reset_payload)); + VERIFY(svg_reset_payload); + computed_values.adopt_svg_reset_group(const_cast(svg_reset_payload)); Array border_group_values; gather_group_values(border_group_properties, border_group_values); @@ -1618,18 +1540,6 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co if (!box_adopted) computed_values.set_display_before_box_type_transformation(computed_style.display_before_box_type_transformation()); - if (!alignment_adopted) - computed_values.set_flex_direction(computed_style.flex_direction()); - if (!alignment_adopted) - computed_values.set_flex_wrap(computed_style.flex_wrap()); - if (!alignment_adopted) - computed_values.set_flex_basis(computed_style.flex_basis()); - if (!alignment_adopted) - computed_values.set_flex_grow(computed_style.flex_grow()); - if (!alignment_adopted) - computed_values.set_flex_shrink(computed_style.flex_shrink()); - if (!alignment_adopted) - computed_values.set_order(computed_style.order()); if (!effects_adopted) computed_values.set_clip(computed_style.clip()); @@ -1638,25 +1548,6 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co if (!effects_adopted) computed_values.set_filter(computed_style.filter()); - if (!svg_reset_adopted) - computed_values.set_flood_color(computed_style.color(CSS::PropertyID::FloodColor, color_resolution_context)); - if (!svg_reset_adopted) - computed_values.set_flood_opacity(computed_style.flood_opacity()); - - if (!alignment_adopted) - computed_values.set_justify_content(computed_style.justify_content()); - if (!alignment_adopted) - computed_values.set_justify_items(computed_style.justify_items()); - if (!alignment_adopted) - computed_values.set_justify_self(computed_style.justify_self()); - - if (!alignment_adopted) - computed_values.set_align_content(computed_style.align_content()); - if (!alignment_adopted) - computed_values.set_align_items(computed_style.align_items()); - if (!alignment_adopted) - computed_values.set_align_self(computed_style.align_self()); - if (!misc_reset_adopted) computed_values.set_appearance(computed_style.appearance()); if (!misc_reset_adopted) @@ -2025,33 +1916,6 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co if (!inherited_box_adopted) computed_values.set_visibility(computed_style.visibility()); - if (!sizing_adopted) - computed_values.set_width(computed_style.size_value(CSS::PropertyID::Width)); - if (!sizing_adopted) - computed_values.set_min_width(computed_style.size_value(CSS::PropertyID::MinWidth)); - if (!sizing_adopted) - computed_values.set_max_width(computed_style.size_value(CSS::PropertyID::MaxWidth)); - - if (!sizing_adopted) - computed_values.set_height(computed_style.size_value(CSS::PropertyID::Height)); - if (!sizing_adopted) - computed_values.set_min_height(computed_style.size_value(CSS::PropertyID::MinHeight)); - if (!sizing_adopted) - computed_values.set_max_height(computed_style.size_value(CSS::PropertyID::MaxHeight)); - - if (!surround_adopted) - computed_values.set_inset(computed_style.length_box(CSS::PropertyID::Left, CSS::PropertyID::Top, CSS::PropertyID::Right, CSS::PropertyID::Bottom, CSS::LengthPercentageOrAuto::make_auto())); - for (auto property_id : { PropertyID::Top, PropertyID::Right, PropertyID::Bottom, PropertyID::Left }) { - if (surround_adopted) - break; - auto const& inset = computed_style.property(property_id); - if (inset.is_anchor()) - computed_values.set_anchor_inset(property_id, inset); - } - if (!surround_adopted) - computed_values.set_margin(computed_style.length_box(CSS::PropertyID::MarginLeft, CSS::PropertyID::MarginTop, CSS::PropertyID::MarginRight, CSS::PropertyID::MarginBottom, CSS::Length::make_px(0))); - if (!surround_adopted) - computed_values.set_padding(computed_style.length_box(CSS::PropertyID::PaddingLeft, CSS::PropertyID::PaddingTop, CSS::PropertyID::PaddingRight, CSS::PropertyID::PaddingBottom, CSS::Length::make_px(0))); if (!misc_reset_adopted) computed_values.set_scroll_margin(computed_style.length_box(CSS::PropertyID::ScrollMarginLeft, CSS::PropertyID::ScrollMarginTop, CSS::PropertyID::ScrollMarginRight, CSS::PropertyID::ScrollMarginBottom, CSS::Length::make_px(0))); if (!misc_reset_adopted) @@ -2188,29 +2052,11 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co if (!grid_adopted) computed_values.set_grid_auto_flow(computed_style.grid_auto_flow()); - if (!svg_reset_adopted) - computed_values.set_cx(CSS::LengthPercentage::from_style_value(computed_style.property(CSS::PropertyID::Cx))); - if (!svg_reset_adopted) - computed_values.set_cy(CSS::LengthPercentage::from_style_value(computed_style.property(CSS::PropertyID::Cy))); - if (!svg_reset_adopted) - computed_values.set_r(CSS::LengthPercentage::from_style_value(computed_style.property(CSS::PropertyID::R))); - if (!svg_reset_adopted) - computed_values.set_rx(CSS::LengthPercentageOrAuto::from_style_value(computed_style.property(CSS::PropertyID::Rx))); - if (!svg_reset_adopted) - computed_values.set_ry(CSS::LengthPercentageOrAuto::from_style_value(computed_style.property(CSS::PropertyID::Ry))); - if (!svg_reset_adopted) - computed_values.set_x(CSS::LengthPercentage::from_style_value(computed_style.property(CSS::PropertyID::X))); - if (!svg_reset_adopted) - computed_values.set_y(CSS::LengthPercentage::from_style_value(computed_style.property(CSS::PropertyID::Y))); - if (!inherited_svg_adopted) computed_values.set_fill(computed_style.fill(color_resolution_context)); if (!inherited_svg_adopted) computed_values.set_stroke(computed_style.stroke(color_resolution_context)); - if (!svg_reset_adopted) - computed_values.set_stop_color(computed_style.color(CSS::PropertyID::StopColor, color_resolution_context)); - auto const& stroke_width = computed_style.property(CSS::PropertyID::StrokeWidth); // FIXME: Converting to pixels isn't really correct - values should be in "user units" // https://svgwg.org/svg2-draft/coords.html#TermUserUnits @@ -2220,8 +2066,6 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co else computed_values.set_stroke_width(CSS::LengthPercentage::from_style_value(stroke_width)); } - if (!svg_reset_adopted) - computed_values.set_shape_rendering(computed_style.shape_rendering()); if (!inherited_svg_adopted) { computed_values.set_paint_order(computed_style.paint_order()); auto const& paint_order = computed_style.property(PropertyID::PaintOrder); @@ -2284,15 +2128,11 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co computed_values.set_stroke_linecap(computed_style.stroke_linecap()); if (!inherited_svg_adopted) computed_values.set_stroke_linejoin(computed_style.stroke_linejoin()); - if (!svg_reset_adopted) - computed_values.set_vector_effect(computed_style.vector_effect()); if (!inherited_svg_adopted) computed_values.set_stroke_miterlimit(computed_style.stroke_miterlimit()); if (!inherited_svg_adopted) computed_values.set_stroke_opacity(computed_style.stroke_opacity()); - if (!svg_reset_adopted) - computed_values.set_stop_opacity(computed_style.stop_opacity()); if (!inherited_svg_adopted) computed_values.set_text_anchor(computed_style.text_anchor()); @@ -2310,11 +2150,6 @@ NonnullRefPtr ComputedValues::create(ComputedProperties co if (!misc_reset_adopted) computed_values.set_column_height(computed_style.size_value(CSS::PropertyID::ColumnHeight)); - if (!alignment_adopted) - computed_values.set_column_gap(computed_style.gap_value(CSS::PropertyID::ColumnGap)); - if (!alignment_adopted) - computed_values.set_row_gap(computed_style.gap_value(CSS::PropertyID::RowGap)); - if (!inherited_table_adopted) computed_values.set_border_collapse(computed_style.border_collapse()); diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index c144f34ae1a88..4fcee675b2ce7 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -276,10 +277,7 @@ struct WillChange { static WillChange make_auto() { return WillChange(); } bool is_auto() const { return m_value.is_empty(); } - bool has_contents() const { return m_value.contains_slow(Type::Contents); } - bool operator==(WillChange const&) const = default; - bool has_scroll_position() const { return m_value.contains_slow(Type::ScrollPosition); } bool has_property(PropertyID property_id) const { return m_value.contains_slow(property_id); } Vector const& entries() const { return m_value; } @@ -1119,11 +1117,16 @@ class WEB_API ComputedValues final : public RefCounted { FontVariantEmoji font_variant_emoji() const { return m_inherited.font->font_variant_emoji; } CSSPixels const& word_spacing() const { return m_inherited.text->word_spacing; } CSSPixels letter_spacing() const { return m_inherited.text->letter_spacing; } - RefPtr word_spacing_style_value() const { return m_inherited.text->word_spacing_style_value; } - RefPtr letter_spacing_style_value() const { return m_inherited.text->letter_spacing_style_value; } - FlexDirection flex_direction() const { return m_noninherited.alignment->flex_direction; } - FlexWrap flex_wrap() const { return m_noninherited.alignment->flex_wrap; } - FlexBasis const& flex_basis() const { return m_noninherited.alignment->flex_basis; } + RefPtr word_spacing_style_value() const; + RefPtr letter_spacing_style_value() const; + FlexDirection flex_direction() const { return static_cast(m_noninherited.alignment->flex_direction); } + FlexWrap flex_wrap() const { return static_cast(m_noninherited.alignment->flex_wrap); } + FlexBasis flex_basis() const + { + if (m_noninherited.alignment->flex_basis.is_content) + return FlexBasisContent {}; + return Size::view(m_noninherited.alignment->flex_basis.size); + } double flex_grow() const { return m_noninherited.alignment->flex_grow; } double flex_shrink() const { return m_noninherited.alignment->flex_shrink; } i32 order() const { return m_noninherited.alignment->order; } @@ -1134,27 +1137,27 @@ class WEB_API ComputedValues final : public RefCounted { return {}; return m_inherited.ui->accent_color.used_value; } - AlignContent align_content() const { return m_noninherited.alignment->align_content; } - AlignItems align_items() const { return m_noninherited.alignment->align_items; } - AlignSelf align_self() const { return m_noninherited.alignment->align_self; } + AlignContent align_content() const { return static_cast(m_noninherited.alignment->align_content); } + AlignItems align_items() const { return static_cast(m_noninherited.alignment->align_items); } + AlignSelf align_self() const { return static_cast(m_noninherited.alignment->align_self); } Appearance appearance() const { return m_noninherited.misc->appearance; } Appearance computed_appearance() const { return m_noninherited.misc->computed_appearance; } float opacity() const { return m_noninherited.effects->opacity; } Visibility visibility() const { return static_cast(m_inherited.box->visibility); } ImageRendering image_rendering() const { return static_cast(m_inherited.box->image_rendering); } - JustifyContent justify_content() const { return m_noninherited.alignment->justify_content; } - JustifySelf justify_self() const { return m_noninherited.alignment->justify_self; } - JustifyItems justify_items() const { return m_noninherited.alignment->justify_items; } + JustifyContent justify_content() const { return static_cast(m_noninherited.alignment->justify_content); } + JustifySelf justify_self() const { return static_cast(m_noninherited.alignment->justify_self); } + JustifyItems justify_items() const { return static_cast(m_noninherited.alignment->justify_items); } Filter const& backdrop_filter() const { return m_noninherited.effects->backdrop_filter; } Filter const& filter() const { return m_noninherited.effects->filter; } Vector const& box_shadow() const { return m_noninherited.effects->box_shadow; } BoxSizing box_sizing() const { return m_noninherited.box->box_sizing; } - Size const& width() const { return m_noninherited.sizing->width; } - Size const& min_width() const { return m_noninherited.sizing->min_width; } - Size const& max_width() const { return m_noninherited.sizing->max_width; } - Size const& height() const { return m_noninherited.sizing->height; } - Size const& min_height() const { return m_noninherited.sizing->min_height; } - Size const& max_height() const { return m_noninherited.sizing->max_height; } + Size const& width() const { return Size::view(m_noninherited.sizing->width); } + Size const& min_width() const { return Size::view(m_noninherited.sizing->min_width); } + Size const& max_width() const { return Size::view(m_noninherited.sizing->max_width); } + Size const& height() const { return Size::view(m_noninherited.sizing->height); } + Size const& min_height() const { return Size::view(m_noninherited.sizing->min_height); } + Size const& max_height() const { return Size::view(m_noninherited.sizing->max_height); } Variant const& vertical_align() const { return m_noninherited.box->vertical_align; } GridTrackSizeList const& grid_auto_columns() const { return m_noninherited.grid->grid_auto_columns; } GridTrackSizeList const& grid_auto_rows() const { return m_noninherited.grid->grid_auto_rows; } @@ -1166,11 +1169,11 @@ class WEB_API ComputedValues final : public RefCounted { GridTrackPlacement const& grid_row_end() const { return m_noninherited.grid->grid_row_end; } GridTrackPlacement const& grid_row_start() const { return m_noninherited.grid->grid_row_start; } ColumnCount column_count() const { return m_noninherited.misc->column_count; } - Variant const& column_gap() const { return m_noninherited.alignment->column_gap; } + Variant column_gap() const { return gap(m_noninherited.alignment->column_gap); } ColumnSpan const& column_span() const { return m_noninherited.misc->column_span; } Size const& column_width() const { return m_noninherited.misc->column_width; } Size const& column_height() const { return m_noninherited.misc->column_height; } - Variant const& row_gap() const { return m_noninherited.alignment->row_gap; } + Variant row_gap() const { return gap(m_noninherited.alignment->row_gap); } BorderCollapse border_collapse() const { return static_cast(m_inherited.table->border_collapse); } EmptyCells empty_cells() const { return static_cast(m_inherited.table->empty_cells); } GridTemplateAreas const& grid_template_areas() const { return m_noninherited.grid->grid_template_areas; } @@ -1217,26 +1220,33 @@ class WEB_API ComputedValues final : public RefCounted { MixBlendMode mix_blend_mode() const { return m_noninherited.effects->mix_blend_mode; } Optional view_transition_name() const { return m_noninherited.misc->view_transition_name; } TouchActionData touch_action() const { return m_noninherited.misc->touch_action; } - ShapeRendering shape_rendering() const { return m_noninherited.svg_reset->shape_rendering; } + ShapeRendering shape_rendering() const { return static_cast(m_noninherited.svg_reset->shape_rendering); } - LengthBox const& inset() const { return m_noninherited.surround->inset; } + LengthBox inset() const { return length_box(m_noninherited.surround->inset); } RefPtr anchor_inset(PropertyID property_id) const { + ComputedValuesFFI::ComputedStyleValueHandle const* handle = nullptr; switch (property_id) { case PropertyID::Top: - return m_noninherited.surround->top_anchor_inset; + handle = &m_noninherited.surround->top_anchor_inset; + break; case PropertyID::Right: - return m_noninherited.surround->right_anchor_inset; + handle = &m_noninherited.surround->right_anchor_inset; + break; case PropertyID::Bottom: - return m_noninherited.surround->bottom_anchor_inset; + handle = &m_noninherited.surround->bottom_anchor_inset; + break; case PropertyID::Left: - return m_noninherited.surround->left_anchor_inset; + handle = &m_noninherited.surround->left_anchor_inset; + break; default: return {}; } + static_assert(sizeof(RustStyleValueHandle) == sizeof(*handle)); + return style_value_from_handle(property_id, reinterpret_cast(*handle)); } - LengthBox const& margin() const { return m_noninherited.surround->margin; } - LengthBox const& padding() const { return m_noninherited.surround->padding; } + LengthBox margin() const { return length_box(m_noninherited.surround->margin); } + LengthBox padding() const { return length_box(m_noninherited.surround->padding); } LengthBox const& scroll_margin() const { return m_noninherited.misc->scroll_margin; } LengthBox const& scroll_padding() const { return m_noninherited.misc->scroll_padding; } OverflowClipMarginData const& overflow_clip_margin() const { return m_noninherited.misc->overflow_clip_margin; } @@ -1265,7 +1275,7 @@ class WEB_API ComputedValues final : public RefCounted { Color color() const { return m_inherited.text->color; } Color background_color() const { return m_noninherited.background->background_color; } - RefPtr background_color_style_value() const { return m_noninherited.background->background_color_style_value; } + RefPtr background_color_style_value() const; BackgroundBox background_color_clip() const { return m_noninherited.background->background_color_clip; } Vector const& background_layers() const { return m_noninherited.background->background_layers; } Vector const& mask_layers() const { return m_noninherited.mask_data->mask_layers; } @@ -1287,11 +1297,11 @@ class WEB_API ComputedValues final : public RefCounted { LengthPercentage const& stroke_dashoffset() const { return m_inherited.svg->stroke_dashoffset; } StrokeLinecap stroke_linecap() const { return m_inherited.svg->stroke_linecap; } StrokeLinejoin stroke_linejoin() const { return m_inherited.svg->stroke_linejoin; } - VectorEffect vector_effect() const { return m_noninherited.svg_reset->vector_effect; } + VectorEffect vector_effect() const { return static_cast(m_noninherited.svg_reset->vector_effect); } double stroke_miterlimit() const { return m_inherited.svg->stroke_miterlimit; } float stroke_opacity() const { return m_inherited.svg->stroke_opacity; } LengthPercentage const& stroke_width() const { return m_inherited.svg->stroke_width; } - Color stop_color() const { return m_noninherited.svg_reset->stop_color; } + Color stop_color() const { return Gfx::Color::from_bgra(m_noninherited.svg_reset->stop_color); } float stop_opacity() const { return m_noninherited.svg_reset->stop_opacity; } TextAnchor text_anchor() const { return m_inherited.svg->text_anchor; } RefPtr mask_image() const { return m_noninherited.mask_data->mask_image; } @@ -1299,19 +1309,19 @@ class WEB_API ComputedValues final : public RefCounted { MaskType mask_type() const { return m_noninherited.mask_data->mask_type; } Optional const& clip_path() const { return m_noninherited.mask_data->clip_path; } ClipRule clip_rule() const { return m_inherited.svg->clip_rule; } - Color flood_color() const { return m_noninherited.svg_reset->flood_color; } + Color flood_color() const { return Gfx::Color::from_bgra(m_noninherited.svg_reset->flood_color); } float flood_opacity() const { return m_noninherited.svg_reset->flood_opacity; } PaintOrderList paint_order() const { return m_inherited.svg->paint_order; } u8 paint_order_serialization_length() const { return m_inherited.svg->paint_order_serialization_length; } bool paint_order_is_normal() const { return m_inherited.svg->paint_order_is_normal; } - LengthPercentage const& cx() const { return m_noninherited.svg_reset->cx; } - LengthPercentage const& cy() const { return m_noninherited.svg_reset->cy; } - LengthPercentage const& r() const { return m_noninherited.svg_reset->r; } - LengthPercentageOrAuto const& rx() const { return m_noninherited.svg_reset->rx; } - LengthPercentageOrAuto const& ry() const { return m_noninherited.svg_reset->ry; } - LengthPercentage const& x() const { return m_noninherited.svg_reset->x; } - LengthPercentage const& y() const { return m_noninherited.svg_reset->y; } + LengthPercentage const& cx() const { return LengthPercentage::view(m_noninherited.svg_reset->cx); } + LengthPercentage const& cy() const { return LengthPercentage::view(m_noninherited.svg_reset->cy); } + LengthPercentage const& r() const { return LengthPercentage::view(m_noninherited.svg_reset->r); } + LengthPercentageOrAuto rx() const { return m_noninherited.svg_reset->rx.is_auto ? LengthPercentageOrAuto::make_auto() : LengthPercentage::view(m_noninherited.svg_reset->rx.value); } + LengthPercentageOrAuto ry() const { return m_noninherited.svg_reset->ry.is_auto ? LengthPercentageOrAuto::make_auto() : LengthPercentage::view(m_noninherited.svg_reset->ry.value); } + LengthPercentage const& x() const { return LengthPercentage::view(m_noninherited.svg_reset->x); } + LengthPercentage const& y() const { return LengthPercentage::view(m_noninherited.svg_reset->y); } Vector> const& transformations() const { return m_noninherited.transform->transformations; } TransformBox const& transform_box() const { return m_noninherited.transform->transform_box; } @@ -1363,6 +1373,32 @@ class WEB_API ComputedValues final : public RefCounted { private: ComputedValues(); + RefPtr style_value_from_handle(PropertyID, RustStyleValueHandle const&) const; + + static LengthPercentageOrAuto length_percentage_or_auto(ComputedValuesFFI::ComputedLengthPercentageOrAuto const& value) + { + if (value.is_auto) + return LengthPercentageOrAuto::make_auto(); + return LengthPercentage::view(value.value); + } + + static LengthBox length_box(ComputedValuesFFI::ComputedLengthBox const& box) + { + return { + length_percentage_or_auto(box.top), + length_percentage_or_auto(box.right), + length_percentage_or_auto(box.bottom), + length_percentage_or_auto(box.left), + }; + } + + static RustStyleValueHandle retain_style_value_data(StyleValue const* value) + { + if (!value) + return {}; + return RustStyleValueHandle { StyleValueFFI::rust_style_value_retain(value->rust_style_value_data()) }; + } + static Statistics s_statistics; static size_t property_bitmap_index(PropertyID property_id) @@ -1374,18 +1410,10 @@ class WEB_API ComputedValues final : public RefCounted { void inherit_from(ComputedValues const& other) { m_inherited = other.m_inherited; } public: - // The layout of this group is defined in Rust (computed_values.rs); see InheritedBoxValues. + // The layout and lifecycle of this group are defined in Rust (computed_values.rs). struct InheritedTableValues : ComputedValuesFFI::InheritedTableValues { static constexpr size_t style_group_index = to_underlying(StyleGroupIndex::InheritedTableValues); - - InheritedTableValues() - { - border_collapse = to_underlying(InitialValues::border_collapse()); - caption_side = to_underlying(InitialValues::caption_side()); - empty_cells = to_underlying(InitialValues::empty_cells()); - border_spacing_horizontal = InitialValues::border_spacing().raw_value(); - border_spacing_vertical = InitialValues::border_spacing().raw_value(); - } + static constexpr auto style_group_lifecycle = ComputedValuesFFI::StyleGroupLifecycle::InheritedTable; bool operator==(InheritedTableValues const& other) const { @@ -1451,7 +1479,7 @@ class WEB_API ComputedValues final : public RefCounted { struct InheritedTextValues { static constexpr size_t style_group_index = to_underlying(StyleGroupIndex::InheritedTextValues); Color color { InitialValues::color() }; - RefPtr color_style_value; + RustStyleValueHandle color_style_value; Color webkit_text_fill_color { InitialValues::color() }; bool webkit_text_fill_color_is_current_color { true }; Vector text_shadow; @@ -1469,29 +1497,21 @@ class WEB_API ComputedValues final : public RefCounted { WordBreak word_break { InitialValues::word_break() }; OverflowWrap overflow_wrap { InitialValues::overflow_wrap() }; CSSPixels word_spacing { InitialValues::word_spacing() }; - RefPtr word_spacing_style_value; + RustStyleValueHandle word_spacing_style_value; CSSPixels letter_spacing { InitialValues::letter_spacing() }; - RefPtr letter_spacing_style_value; + RustStyleValueHandle letter_spacing_style_value; u64 orphans { InitialValues::orphans() }; u64 widows { InitialValues::widows() }; bool operator==(InheritedTextValues const&) const = default; }; - // The layout of this group is defined in Rust (computed_values.rs); this type only adds - // the initial values on top of the mirrored layout. The fields hold the underlying values - // of the corresponding C++ enums, and the lens getters and setters convert. + // The layout and lifecycle of this group are defined in Rust (computed_values.rs). The + // fields hold the underlying values of the corresponding C++ enums, and the lens getters + // and setters convert. struct InheritedBoxValues : ComputedValuesFFI::InheritedBoxValues { static constexpr size_t style_group_index = to_underlying(StyleGroupIndex::InheritedBoxValues); - - InheritedBoxValues() - { - visibility = to_underlying(InitialValues::visibility()); - direction = to_underlying(InitialValues::direction()); - writing_mode = to_underlying(InitialValues::writing_mode()); - content_visibility = to_underlying(InitialValues::content_visibility()); - image_rendering = to_underlying(InitialValues::image_rendering()); - } + static constexpr auto style_group_lifecycle = ComputedValuesFFI::StyleGroupLifecycle::InheritedBox; bool operator==(InheritedBoxValues const& other) const { @@ -1571,23 +1591,32 @@ class WEB_API ComputedValues final : public RefCounted { bool operator==(AnimationValues const&) const = default; }; - struct SVGResetValues { + // The layout and lifecycle of this group are defined in Rust (computed_values.rs). + struct SVGResetValues : ComputedValuesFFI::SVGResetValues { static constexpr size_t style_group_index = to_underlying(StyleGroupIndex::SVGResetValues); - LengthPercentage cx { InitialValues::cx() }; - LengthPercentage cy { InitialValues::cy() }; - LengthPercentage r { InitialValues::r() }; - LengthPercentageOrAuto rx { InitialValues::rx() }; - LengthPercentageOrAuto ry { InitialValues::ry() }; - LengthPercentage x { InitialValues::x() }; - LengthPercentage y { InitialValues::y() }; - Gfx::Color stop_color { InitialValues::stop_color() }; - float stop_opacity { InitialValues::stop_opacity() }; - Color flood_color { InitialValues::flood_color() }; - float flood_opacity { InitialValues::flood_opacity() }; - VectorEffect vector_effect { InitialValues::vector_effect() }; - ShapeRendering shape_rendering { InitialValues::shape_rendering() }; - - bool operator==(SVGResetValues const&) const = default; + static constexpr auto style_group_lifecycle = ComputedValuesFFI::StyleGroupLifecycle::SVGReset; + + bool operator==(SVGResetValues const& other) const + { + auto length_percentage_or_auto_equal = [](auto const& first, auto const& second) { + if (first.is_auto || second.is_auto) + return first.is_auto == second.is_auto; + return LengthPercentage::view(first.value) == LengthPercentage::view(second.value); + }; + return LengthPercentage::view(cx) == LengthPercentage::view(other.cx) + && LengthPercentage::view(cy) == LengthPercentage::view(other.cy) + && LengthPercentage::view(r) == LengthPercentage::view(other.r) + && length_percentage_or_auto_equal(rx, other.rx) + && length_percentage_or_auto_equal(ry, other.ry) + && LengthPercentage::view(x) == LengthPercentage::view(other.x) + && LengthPercentage::view(y) == LengthPercentage::view(other.y) + && stop_color == other.stop_color + && stop_opacity == other.stop_opacity + && flood_color == other.flood_color + && flood_opacity == other.flood_opacity + && vector_effect == other.vector_effect + && shape_rendering == other.shape_rendering; + } }; struct GridValues { @@ -1698,7 +1727,7 @@ class WEB_API ComputedValues final : public RefCounted { struct BackgroundValues { static constexpr size_t style_group_index = to_underlying(StyleGroupIndex::BackgroundValues); Color background_color { InitialValues::background_color() }; - RefPtr background_color_style_value; + RustStyleValueHandle background_color_style_value; BackgroundBox background_color_clip { InitialValues::background_color_clip() }; Vector background_layers { BackgroundLayerData {} }; @@ -1711,10 +1740,10 @@ class WEB_API ComputedValues final : public RefCounted { BorderData border_top; BorderData border_right; BorderData border_bottom; - RefPtr border_left_color_style_value; - RefPtr border_top_color_style_value; - RefPtr border_right_color_style_value; - RefPtr border_bottom_color_style_value; + RustStyleValueHandle border_left_color_style_value; + RustStyleValueHandle border_top_color_style_value; + RustStyleValueHandle border_right_color_style_value; + RustStyleValueHandle border_bottom_color_style_value; CSSPixels border_left_computed_width { 0 }; CSSPixels border_top_computed_width { 0 }; CSSPixels border_right_computed_width { 0 }; @@ -1733,24 +1762,33 @@ class WEB_API ComputedValues final : public RefCounted { bool operator==(BorderValues const&) const = default; }; - struct AlignmentValues { + struct AlignmentValues : ComputedValuesFFI::AlignmentValues { static constexpr size_t style_group_index = to_underlying(StyleGroupIndex::AlignmentValues); - FlexDirection flex_direction { InitialValues::flex_direction() }; - FlexWrap flex_wrap { InitialValues::flex_wrap() }; - FlexBasis flex_basis { InitialValues::flex_basis() }; - double flex_grow { InitialValues::flex_grow() }; - double flex_shrink { InitialValues::flex_shrink() }; - i32 order { InitialValues::order() }; - AlignContent align_content { InitialValues::align_content() }; - AlignItems align_items { InitialValues::align_items() }; - AlignSelf align_self { InitialValues::align_self() }; - JustifyContent justify_content { InitialValues::justify_content() }; - JustifyItems justify_items { InitialValues::justify_items() }; - JustifySelf justify_self { InitialValues::justify_self() }; - Variant column_gap { InitialValues::column_gap() }; - Variant row_gap { InitialValues::row_gap() }; - - bool operator==(AlignmentValues const&) const = default; + static constexpr auto style_group_lifecycle = ComputedValuesFFI::StyleGroupLifecycle::Alignment; + + bool operator==(AlignmentValues const& other) const + { + auto gaps_equal = [](auto const& first, auto const& second) { + if (first.is_normal || second.is_normal) + return first.is_normal == second.is_normal; + return LengthPercentage::view(first.value) == LengthPercentage::view(second.value); + }; + return flex_direction == other.flex_direction + && flex_wrap == other.flex_wrap + && flex_basis.is_content == other.flex_basis.is_content + && Size::view(flex_basis.size) == Size::view(other.flex_basis.size) + && flex_grow == other.flex_grow + && flex_shrink == other.flex_shrink + && order == other.order + && align_content == other.align_content + && align_items == other.align_items + && align_self == other.align_self + && justify_content == other.justify_content + && justify_items == other.justify_items + && justify_self == other.justify_self + && gaps_equal(column_gap, other.column_gap) + && gaps_equal(row_gap, other.row_gap); + } }; struct MiscResetValues { @@ -1785,29 +1823,46 @@ class WEB_API ComputedValues final : public RefCounted { bool operator==(MiscResetValues const&) const = default; }; - struct SizingValues { + struct SizingValues : ComputedValuesFFI::SizingValues { static constexpr size_t style_group_index = to_underlying(StyleGroupIndex::SizingValues); - Size width { InitialValues::width() }; - Size min_width { InitialValues::min_width() }; - Size max_width { InitialValues::max_width() }; - Size height { InitialValues::height() }; - Size min_height { InitialValues::min_height() }; - Size max_height { InitialValues::max_height() }; - - bool operator==(SizingValues const&) const = default; + static constexpr auto style_group_lifecycle = ComputedValuesFFI::StyleGroupLifecycle::Sizing; + + bool operator==(SizingValues const& other) const + { + return Size::view(width) == Size::view(other.width) + && Size::view(min_width) == Size::view(other.min_width) + && Size::view(max_width) == Size::view(other.max_width) + && Size::view(height) == Size::view(other.height) + && Size::view(min_height) == Size::view(other.min_height) + && Size::view(max_height) == Size::view(other.max_height); + } }; - struct SurroundValues { + struct SurroundValues : ComputedValuesFFI::SurroundValues { static constexpr size_t style_group_index = to_underlying(StyleGroupIndex::SurroundValues); - LengthBox inset { InitialValues::inset() }; - RefPtr top_anchor_inset; - RefPtr right_anchor_inset; - RefPtr bottom_anchor_inset; - RefPtr left_anchor_inset; - LengthBox margin { InitialValues::margin() }; - LengthBox padding { InitialValues::padding() }; - - bool operator==(SurroundValues const&) const = default; + static constexpr auto style_group_lifecycle = ComputedValuesFFI::StyleGroupLifecycle::Surround; + + bool operator==(SurroundValues const& other) const + { + auto side_equal = [](auto const& first, auto const& second) { + if (first.is_auto || second.is_auto) + return first.is_auto == second.is_auto; + return LengthPercentage::view(first.value) == LengthPercentage::view(second.value); + }; + auto box_equal = [&](auto const& first, auto const& second) { + return side_equal(first.top, second.top) + && side_equal(first.right, second.right) + && side_equal(first.bottom, second.bottom) + && side_equal(first.left, second.left); + }; + return box_equal(inset, other.inset) + && top_anchor_inset.pointer == other.top_anchor_inset.pointer + && right_anchor_inset.pointer == other.right_anchor_inset.pointer + && bottom_anchor_inset.pointer == other.bottom_anchor_inset.pointer + && left_anchor_inset.pointer == other.left_anchor_inset.pointer + && box_equal(margin, other.margin) + && box_equal(padding, other.padding); + } }; struct BoxValues { @@ -1835,6 +1890,13 @@ class WEB_API ComputedValues final : public RefCounted { }; private: + static Variant gap(ComputedValuesFFI::ComputedGap const& gap) + { + if (gap.is_normal) + return NormalGap {}; + return LengthPercentage::view(gap.value); + } + struct NonInheritedValues { StyleStructRef animation; StyleStructRef box; @@ -1858,6 +1920,7 @@ class WEB_API ComputedValues final : public RefCounted { AK::FixedBitmap m_property_important { false }; AK::FixedBitmap m_property_inherited { false }; HashMap> m_inheritance_dependent_specified_values; + mutable HashMap> m_style_value_cache; RefPtr m_raw_cascaded_font_size; RefPtr m_base_values; RefPtr m_animated_properties; @@ -2113,9 +2176,9 @@ class ComputedValues::Mutator final { } void set_color_style_value(StyleValue const* value) { - if (m_values.m_inherited.text->color_style_value == value) + if (m_values.m_inherited.text->color_style_value.data() == (value ? value->rust_style_value_data() : nullptr)) return; - m_values.m_inherited.text.access().color_style_value = value; + m_values.m_inherited.text.access().color_style_value = retain_style_value_data(value); } void set_color_interpolation(ColorInterpolation color_interpolation) { @@ -2193,9 +2256,9 @@ class ComputedValues::Mutator final { } void set_background_color_style_value(StyleValue const& value) { - if (m_values.m_noninherited.background->background_color_style_value == &value) + if (m_values.m_noninherited.background->background_color_style_value.data() == value.rust_style_value_data()) return; - m_values.m_noninherited.background.access().background_color_style_value = value; + m_values.m_noninherited.background.access().background_color_style_value = retain_style_value_data(&value); } void set_background_color_clip(BackgroundBox box) { @@ -2477,15 +2540,15 @@ class ComputedValues::Mutator final { } void set_word_spacing_style_value(StyleValue const& value) { - if (m_values.m_inherited.text->word_spacing_style_value == &value) + if (m_values.m_inherited.text->word_spacing_style_value.data() == value.rust_style_value_data()) return; - m_values.m_inherited.text.access().word_spacing_style_value = value; + m_values.m_inherited.text.access().word_spacing_style_value = retain_style_value_data(&value); } void set_letter_spacing_style_value(StyleValue const& value) { - if (m_values.m_inherited.text->letter_spacing_style_value == &value) + if (m_values.m_inherited.text->letter_spacing_style_value.data() == value.rust_style_value_data()) return; - m_values.m_inherited.text.access().letter_spacing_style_value = value; + m_values.m_inherited.text.access().letter_spacing_style_value = retain_style_value_data(&value); } void set_word_break(WordBreak value) { @@ -2523,83 +2586,23 @@ class ComputedValues::Mutator final { return; m_values.m_inherited.text.access().letter_spacing = value; } - void set_width(Size width) - { - if (m_values.m_noninherited.sizing->width == width) - return; - m_values.m_noninherited.sizing.access().width = width; - } - void set_min_width(Size width) - { - if (m_values.m_noninherited.sizing->min_width == width) - return; - m_values.m_noninherited.sizing.access().min_width = width; - } - void set_max_width(Size width) - { - if (m_values.m_noninherited.sizing->max_width == width) - return; - m_values.m_noninherited.sizing.access().max_width = width; - } - void set_height(Size height) - { - if (m_values.m_noninherited.sizing->height == height) - return; - m_values.m_noninherited.sizing.access().height = height; - } - void set_min_height(Size height) - { - if (m_values.m_noninherited.sizing->min_height == height) - return; - m_values.m_noninherited.sizing.access().min_height = height; - } - void set_max_height(Size height) - { - if (m_values.m_noninherited.sizing->max_height == height) - return; - m_values.m_noninherited.sizing.access().max_height = height; - } + void set_width(Size value) { set_size(&ComputedValuesFFI::SizingValues::width, move(value)); } + void set_min_width(Size value) { set_size(&ComputedValuesFFI::SizingValues::min_width, move(value)); } + void set_max_width(Size value) { set_size(&ComputedValuesFFI::SizingValues::max_width, move(value)); } + void set_height(Size value) { set_size(&ComputedValuesFFI::SizingValues::height, move(value)); } + void set_min_height(Size value) { set_size(&ComputedValuesFFI::SizingValues::min_height, move(value)); } + void set_max_height(Size value) { set_size(&ComputedValuesFFI::SizingValues::max_height, move(value)); } void set_inset(LengthBox const& inset) { - if (m_values.m_noninherited.surround->inset == inset) - return; - m_values.m_noninherited.surround.access().inset = inset; - } - void set_anchor_inset(PropertyID property_id, RefPtr value) - { - auto set = [&](RefPtr SurroundValues::* member) { - if ((*m_values.m_noninherited.surround).*member == value) - return; - m_values.m_noninherited.surround.access().*member = move(value); - }; - switch (property_id) { - case PropertyID::Top: - set(&SurroundValues::top_anchor_inset); - return; - case PropertyID::Right: - set(&SurroundValues::right_anchor_inset); - return; - case PropertyID::Bottom: - set(&SurroundValues::bottom_anchor_inset); - return; - case PropertyID::Left: - set(&SurroundValues::left_anchor_inset); + if (m_values.inset() == inset) return; - default: - VERIFY_NOT_REACHED(); - } + set_length_box(m_values.m_noninherited.surround.access().inset, inset); } void set_margin(LengthBox const& margin) { - if (m_values.m_noninherited.surround->margin == margin) - return; - m_values.m_noninherited.surround.access().margin = margin; - } - void set_padding(LengthBox const& padding) - { - if (m_values.m_noninherited.surround->padding == padding) + if (m_values.margin() == margin) return; - m_values.m_noninherited.surround.access().padding = padding; + set_length_box(m_values.m_noninherited.surround.access().margin, margin); } void set_scroll_margin(LengthBox value) { @@ -2763,27 +2766,27 @@ class ComputedValues::Mutator final { } void set_border_left_color_style_value(StyleValue const& value) { - if (m_values.m_noninherited.border->border_left_color_style_value == &value) + if (m_values.m_noninherited.border->border_left_color_style_value.data() == value.rust_style_value_data()) return; - m_values.m_noninherited.border.access().border_left_color_style_value = value; + m_values.m_noninherited.border.access().border_left_color_style_value = retain_style_value_data(&value); } void set_border_top_color_style_value(StyleValue const& value) { - if (m_values.m_noninherited.border->border_top_color_style_value == &value) + if (m_values.m_noninherited.border->border_top_color_style_value.data() == value.rust_style_value_data()) return; - m_values.m_noninherited.border.access().border_top_color_style_value = value; + m_values.m_noninherited.border.access().border_top_color_style_value = retain_style_value_data(&value); } void set_border_right_color_style_value(StyleValue const& value) { - if (m_values.m_noninherited.border->border_right_color_style_value == &value) + if (m_values.m_noninherited.border->border_right_color_style_value.data() == value.rust_style_value_data()) return; - m_values.m_noninherited.border.access().border_right_color_style_value = value; + m_values.m_noninherited.border.access().border_right_color_style_value = retain_style_value_data(&value); } void set_border_bottom_color_style_value(StyleValue const& value) { - if (m_values.m_noninherited.border->border_bottom_color_style_value == &value) + if (m_values.m_noninherited.border->border_bottom_color_style_value.data() == value.rust_style_value_data()) return; - m_values.m_noninherited.border.access().border_bottom_color_style_value = value; + m_values.m_noninherited.border.access().border_bottom_color_style_value = retain_style_value_data(&value); } void set_border_left_computed_width(CSSPixels value) { @@ -2811,21 +2814,15 @@ class ComputedValues::Mutator final { } void set_flex_direction(FlexDirection value) { - if (m_values.m_noninherited.alignment->flex_direction == value) + if (m_values.flex_direction() == value) return; - m_values.m_noninherited.alignment.access().flex_direction = value; + m_values.m_noninherited.alignment.access().flex_direction = to_underlying(value); } void set_flex_wrap(FlexWrap value) { - if (m_values.m_noninherited.alignment->flex_wrap == value) + if (m_values.flex_wrap() == value) return; - m_values.m_noninherited.alignment.access().flex_wrap = value; - } - void set_flex_basis(FlexBasis value) - { - if (m_values.m_noninherited.alignment->flex_basis == value) - return; - m_values.m_noninherited.alignment.access().flex_basis = move(value); + m_values.m_noninherited.alignment.access().flex_wrap = to_underlying(value); } void set_flex_grow(double value) { @@ -2853,21 +2850,21 @@ class ComputedValues::Mutator final { } void set_align_content(AlignContent value) { - if (m_values.m_noninherited.alignment->align_content == value) + if (m_values.align_content() == value) return; - m_values.m_noninherited.alignment.access().align_content = value; + m_values.m_noninherited.alignment.access().align_content = to_underlying(value); } void set_align_items(AlignItems value) { - if (m_values.m_noninherited.alignment->align_items == value) + if (m_values.align_items() == value) return; - m_values.m_noninherited.alignment.access().align_items = value; + m_values.m_noninherited.alignment.access().align_items = to_underlying(value); } void set_align_self(AlignSelf value) { - if (m_values.m_noninherited.alignment->align_self == value) + if (m_values.align_self() == value) return; - m_values.m_noninherited.alignment.access().align_self = value; + m_values.m_noninherited.alignment.access().align_self = to_underlying(value); } void set_appearance(Appearance value) { @@ -2889,21 +2886,21 @@ class ComputedValues::Mutator final { } void set_justify_content(JustifyContent value) { - if (m_values.m_noninherited.alignment->justify_content == value) + if (m_values.justify_content() == value) return; - m_values.m_noninherited.alignment.access().justify_content = value; + m_values.m_noninherited.alignment.access().justify_content = to_underlying(value); } void set_justify_items(JustifyItems value) { - if (m_values.m_noninherited.alignment->justify_items == value) + if (m_values.justify_items() == value) return; - m_values.m_noninherited.alignment.access().justify_items = value; + m_values.m_noninherited.alignment.access().justify_items = to_underlying(value); } void set_justify_self(JustifySelf value) { - if (m_values.m_noninherited.alignment->justify_self == value) + if (m_values.justify_self() == value) return; - m_values.m_noninherited.alignment.access().justify_self = value; + m_values.m_noninherited.alignment.access().justify_self = to_underlying(value); } void set_box_shadow(Vector&& value) { @@ -3037,12 +3034,6 @@ class ComputedValues::Mutator final { return; m_values.m_noninherited.misc.access().column_count = value; } - void set_column_gap(Variant column_gap) - { - if (m_values.m_noninherited.alignment->column_gap == column_gap) - return; - m_values.m_noninherited.alignment.access().column_gap = column_gap; - } void set_column_span(ColumnSpan column_span) { if (m_values.m_noninherited.misc->column_span == column_span) @@ -3061,12 +3052,6 @@ class ComputedValues::Mutator final { return; m_values.m_noninherited.misc.access().column_height = column_height; } - void set_row_gap(Variant row_gap) - { - if (m_values.m_noninherited.alignment->row_gap == row_gap) - return; - m_values.m_noninherited.alignment.access().row_gap = row_gap; - } void set_border_collapse(BorderCollapse const border_collapse) { if (m_values.m_inherited.table->border_collapse == to_underlying(border_collapse)) @@ -3236,12 +3221,6 @@ class ComputedValues::Mutator final { return; m_values.m_inherited.svg.access().stroke_linejoin = value; } - void set_vector_effect(VectorEffect value) - { - if (m_values.m_noninherited.svg_reset->vector_effect == value) - return; - m_values.m_noninherited.svg_reset.access().vector_effect = value; - } void set_stroke_miterlimit(double value) { if (m_values.m_inherited.svg->stroke_miterlimit == value) @@ -3260,18 +3239,6 @@ class ComputedValues::Mutator final { return; m_values.m_inherited.svg.access().stroke_width = move(value); } - void set_stop_color(Color value) - { - if (m_values.m_noninherited.svg_reset->stop_color == value) - return; - m_values.m_noninherited.svg_reset.access().stop_color = value; - } - void set_stop_opacity(float value) - { - if (m_values.m_noninherited.svg_reset->stop_opacity == value) - return; - m_values.m_noninherited.svg_reset.access().stop_opacity = value; - } void set_text_anchor(TextAnchor value) { if (m_values.m_inherited.svg->text_anchor == value) @@ -3338,24 +3305,6 @@ class ComputedValues::Mutator final { return; m_values.m_inherited.svg.access().clip_rule = value; } - void set_flood_color(Color value) - { - if (m_values.m_noninherited.svg_reset->flood_color == value) - return; - m_values.m_noninherited.svg_reset.access().flood_color = value; - } - void set_flood_opacity(float value) - { - if (m_values.m_noninherited.svg_reset->flood_opacity == value) - return; - m_values.m_noninherited.svg_reset.access().flood_opacity = value; - } - void set_shape_rendering(ShapeRendering value) - { - if (m_values.m_noninherited.svg_reset->shape_rendering == value) - return; - m_values.m_noninherited.svg_reset.access().shape_rendering = value; - } void set_paint_order(PaintOrderList value) { if (m_values.m_inherited.svg->paint_order == value) @@ -3371,49 +3320,6 @@ class ComputedValues::Mutator final { svg.paint_order_is_normal = is_normal; } - void set_cx(LengthPercentage cx) - { - if (m_values.m_noninherited.svg_reset->cx == cx) - return; - m_values.m_noninherited.svg_reset.access().cx = move(cx); - } - void set_cy(LengthPercentage cy) - { - if (m_values.m_noninherited.svg_reset->cy == cy) - return; - m_values.m_noninherited.svg_reset.access().cy = move(cy); - } - void set_r(LengthPercentage r) - { - if (m_values.m_noninherited.svg_reset->r == r) - return; - m_values.m_noninherited.svg_reset.access().r = move(r); - } - void set_rx(LengthPercentageOrAuto rx) - { - if (m_values.m_noninherited.svg_reset->rx == rx) - return; - m_values.m_noninherited.svg_reset.access().rx = move(rx); - } - void set_ry(LengthPercentageOrAuto ry) - { - if (m_values.m_noninherited.svg_reset->ry == ry) - return; - m_values.m_noninherited.svg_reset.access().ry = move(ry); - } - void set_x(LengthPercentage x) - { - if (m_values.m_noninherited.svg_reset->x == x) - return; - m_values.m_noninherited.svg_reset.access().x = move(x); - } - void set_y(LengthPercentage y) - { - if (m_values.m_noninherited.svg_reset->y == y) - return; - m_values.m_noninherited.svg_reset.access().y = move(y); - } - void set_math_shift(MathShift value) { if (m_values.m_inherited.font->math_shift == value) @@ -3509,6 +3415,33 @@ class ComputedValues::Mutator final { } private: + static void replace_length_percentage_or_auto(ComputedValuesFFI::ComputedLengthPercentageOrAuto& target, LengthPercentageOrAuto const& replacement) + { + StyleValueFFI::rust_style_value_release(static_cast(target.value.pointer)); + target.is_auto = replacement.is_auto(); + if (replacement.is_auto()) { + target.value.pointer = nullptr; + return; + } + auto retained = replacement.length_percentage(); + target.value.pointer = retained.leak_data(); + } + + static void set_length_box(ComputedValuesFFI::ComputedLengthBox& target, LengthBox const& replacement) + { + replace_length_percentage_or_auto(target.top, replacement.top()); + replace_length_percentage_or_auto(target.right, replacement.right()); + replace_length_percentage_or_auto(target.bottom, replacement.bottom()); + replace_length_percentage_or_auto(target.left, replacement.left()); + } + + void set_size(ComputedValuesFFI::ComputedSize ComputedValuesFFI::SizingValues::* member, Size value) + { + if (Size::view(m_values.m_noninherited.sizing.operator->()->*member) == value) + return; + Size::replace(m_values.m_noninherited.sizing.access().*member, move(value)); + } + ComputedValues& m_values; }; diff --git a/Libraries/LibWeb/CSS/ContainerQuery.h b/Libraries/LibWeb/CSS/ContainerQuery.h index 3387413e68d3a..09034e31aa06f 100644 --- a/Libraries/LibWeb/CSS/ContainerQuery.h +++ b/Libraries/LibWeb/CSS/ContainerQuery.h @@ -108,7 +108,6 @@ class WEB_API ContainerQuery final : public RefCounted { static NonnullRefPtr create(NonnullOwnPtr&&); bool matches() const { return m_matches; } - ContainerQueryFeatureRequirements const& feature_requirements() const { return m_feature_requirements; } bool contains_size_feature() const { return m_feature_requirements.contains_size_feature(); } bool contains_style_feature() const { return m_feature_requirements.contains_style_feature(); } MatchResult evaluate(DOM::AbstractElement const&, Optional const& container_name) const; diff --git a/Libraries/LibWeb/CSS/CustomPropertyData.cpp b/Libraries/LibWeb/CSS/CustomPropertyData.cpp index b5819c411acc2..6d921cf82e9c5 100644 --- a/Libraries/LibWeb/CSS/CustomPropertyData.cpp +++ b/Libraries/LibWeb/CSS/CustomPropertyData.cpp @@ -32,8 +32,7 @@ CustomPropertyData::CustomPropertyData(OrderedHashMaprust_style_value_data(), + .data = StyleValueFFI::rust_style_value_retain(property.value->rust_style_value_data()), }); } m_rust_store = ComputedValuesFFI::rust_custom_property_store_create( diff --git a/Libraries/LibWeb/CSS/EasingFunction.cpp b/Libraries/LibWeb/CSS/EasingFunction.cpp index 02ee900c0fc17..ecc7d63f79863 100644 --- a/Libraries/LibWeb/CSS/EasingFunction.cpp +++ b/Libraries/LibWeb/CSS/EasingFunction.cpp @@ -19,195 +19,58 @@ namespace Web::CSS { // https://drafts.csswg.org/css-easing/#linear-easing-function-output double LinearEasingFunction::evaluate_at(double input_progress, bool before_flag) const { - // To calculate linear easing output progress for a given linear easing function func, - // an input progress value inputProgress, and an optional before flag (defaulting to false), - // perform the following: - - // 1. Let points be func’s control points. - - // 2. If points holds only a single item, return the output progress value of that item. - if (control_points.size() == 1) - return control_points[0].output; - - // 3. If inputProgress matches the input progress value of the first point in points, - // and the before flag is true, return the first point’s output progress value. - if (input_progress == control_points[0].input.value() && before_flag) - return control_points[0].output; - - // 4. If inputProgress matches the input progress value of at least one point in points, - // return the output progress value of the last such point. - auto maybe_match = control_points.last_matching([&](auto& stop) { return input_progress == stop.input; }); - if (maybe_match.has_value()) - return maybe_match->output; - - // 5. Otherwise, find two control points in points, A and B, which will be used for interpolation: - ControlPoint A; - ControlPoint B; - - if (input_progress < control_points[0].input.value()) { - // 1. If inputProgress is smaller than any input progress value in points, - // let A and B be the first two items in points. - // If A and B have the same input progress value, return A’s output progress value. - A = control_points[0]; - B = control_points[1]; - if (A.input == B.input.value()) - return A.output; - } else if (input_progress > control_points.last().input.value()) { - // 2. If inputProgress is larger than any input progress value in points, - // let A and B be the last two items in points. - // If A and B have the same input progress value, return B’s output progress value. - A = control_points[control_points.size() - 2]; - B = control_points[control_points.size() - 1]; - if (A.input == B.input.value()) - return B.output; - } else { - // 3. Otherwise, let A be the last control point whose input progress value is smaller than inputProgress, - // and let B be the first control point whose input progress value is larger than inputProgress. - A = control_points.last_matching([&](ControlPoint const& stop) { return stop.input.value() < input_progress; }).value(); - B = control_points.first_matching([&](ControlPoint const& stop) { return stop.input.value() > input_progress; }).value(); + Vector points; + points.ensure_capacity(control_points.size()); + for (auto const& point : control_points) { + VERIFY(point.input.has_value()); + points.unchecked_append({ .input = *point.input, .output = point.output }); } - - // 6. Linearly interpolate (or extrapolate) inputProgress along the line defined by A and B, and return the result. - auto factor = (input_progress - A.input.value()) / (B.input.value() - A.input.value()); - return A.output + factor * (B.output - A.output); + StyleValueFFI::FfiEasingDescriptor descriptor { + .kind = StyleValueFFI::FfiEasingKind::Linear, + .linear_points = points.data(), + .linear_point_count = points.size(), + .x1 = 0, + .y1 = 0, + .x2 = 0, + .y2 = 0, + .interval_count = 0, + .step_position = 0, + }; + return StyleValueFFI::rust_evaluate_easing(&descriptor, input_progress, before_flag); } // https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo -double CubicBezierEasingFunction::evaluate_at(double input_progress, bool) const +double CubicBezierEasingFunction::evaluate_at(double input_progress, bool before_flag) const { - constexpr static auto cubic_bezier_at = [](double x1, double x2, double t) { - auto a = 1.0 - 3.0 * x2 + 3.0 * x1; - auto b = 3.0 * x2 - 6.0 * x1; - auto c = 3.0 * x1; - - auto t2 = t * t; - auto t3 = t2 * t; - - return (a * t3) + (b * t2) + (c * t); - }; - - // For input progress values outside the range [0, 1], the curve is extended infinitely using tangent of the curve - // at the closest endpoint as follows: - - // - For input progress values less than zero, - if (input_progress < 0.0) { - // 1. If the x value of P1 is greater than zero, use a straight line that passes through P1 and P0 as the - // tangent. - if (x1 > 0.0) - return y1 / x1 * input_progress; - - // 2. Otherwise, if the x value of P2 is greater than zero, use a straight line that passes through P2 and P0 as - // the tangent. - if (x2 > 0.0) - return y2 / x2 * input_progress; - - // 3. Otherwise, let the output progress value be zero for all input progress values in the range [-∞, 0). - return 0.0; - } - - // - For input progress values greater than one, - if (input_progress > 1.0) { - // 1. If the x value of P2 is less than one, use a straight line that passes through P2 and P3 as the tangent. - if (x2 < 1.0) - return (1.0 - y2) / (1.0 - x2) * (input_progress - 1.0) + 1.0; - - // 2. Otherwise, if the x value of P1 is less than one, use a straight line that passes through P1 and P3 as the - // tangent. - if (x1 < 1.0) - return (1.0 - y1) / (1.0 - x1) * (input_progress - 1.0) + 1.0; - - // 3. Otherwise, let the output progress value be one for all input progress values in the range (1, ∞]. - return 1.0; - } - - // Note: The spec does not specify the precise algorithm for calculating values in the range [0, 1]: - // "The evaluation of this curve is covered in many sources such as [FUND-COMP-GRAPHICS]." - - // We use Newton-Raphson iteration to solve for the parameter t where x(t) = input_progress, - // then return y(t). Falls back to bisection when Newton-Raphson doesn't converge. - - constexpr static auto cubic_bezier_derivative_at = [](double x1, double x2, double t) { - auto a = 1.0 - 3.0 * x2 + 3.0 * x1; - auto b = 3.0 * x2 - 6.0 * x1; - auto c = 3.0 * x1; - return 3.0 * a * t * t + 2.0 * b * t + c; + StyleValueFFI::FfiEasingDescriptor descriptor { + .kind = StyleValueFFI::FfiEasingKind::CubicBezier, + .linear_points = nullptr, + .linear_point_count = 0, + .x1 = x1, + .y1 = y1, + .x2 = x2, + .y2 = y2, + .interval_count = 0, + .step_position = 0, }; - - constexpr double epsilon = 1e-7; - auto x = input_progress; - - // Newton-Raphson iteration. - auto t = x; - for (int i = 0; i < 8; ++i) { - auto x_at_t = cubic_bezier_at(x1, x2, t) - x; - if (AK::fabs(x_at_t) < epsilon) - return cubic_bezier_at(y1, y2, t); - auto dx = cubic_bezier_derivative_at(x1, x2, t); - if (AK::fabs(dx) < 1e-12) - break; - t -= x_at_t / dx; - } - - // Bisection fallback. - double lo = 0.0; - double hi = 1.0; - t = x; - for (int i = 0; i < 64; ++i) { - auto x_at_t = cubic_bezier_at(x1, x2, t); - if (AK::fabs(x_at_t - x) < epsilon) - return cubic_bezier_at(y1, y2, t); - if (x > x_at_t) - lo = t; - else - hi = t; - t = (lo + hi) / 2.0; - } - - return cubic_bezier_at(y1, y2, t); + return StyleValueFFI::rust_evaluate_easing(&descriptor, input_progress, before_flag); } // https://www.w3.org/TR/css-easing-1/#step-easing-algo double StepsEasingFunction::evaluate_at(double input_progress, bool before_flag) const { - auto current_step = floor(input_progress * interval_count); - - // 2. If the step position property is one of: - // - jump-start, - // - jump-both, - // increment current step by one. - if (position == StepPosition::JumpStart || position == StepPosition::Start || position == StepPosition::JumpBoth) - current_step += 1; - - // 3. If both of the following conditions are true: - // - the before flag is set, and - // - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then - // decrement current step by one. - auto step_progress = input_progress * interval_count; - if (before_flag && trunc(step_progress) == step_progress) - current_step -= 1; - - // 4. If input progress value ≥ 0 and current step < 0, let current step be zero. - if (input_progress >= 0.0 && current_step < 0.0) - current_step = 0.0; - - // 5. Calculate jumps based on the step position as follows: - - // jump-start or jump-end -> steps - // jump-none -> steps - 1 - // jump-both -> steps + 1 - auto jumps = interval_count; - if (position == StepPosition::JumpNone) { - jumps--; - } else if (position == StepPosition::JumpBoth) { - jumps++; - } - - // 6. If input progress value ≤ 1 and current step > jumps, let current step be jumps. - if (input_progress <= 1.0 && current_step > jumps) - current_step = jumps; - - // 7. The output progress value is current step / jumps. - return current_step / jumps; + StyleValueFFI::FfiEasingDescriptor descriptor { + .kind = StyleValueFFI::FfiEasingKind::Steps, + .linear_points = nullptr, + .linear_point_count = 0, + .x1 = 0, + .y1 = 0, + .x2 = 0, + .y2 = 0, + .interval_count = interval_count, + .step_position = to_underlying(position), + }; + return StyleValueFFI::rust_evaluate_easing(&descriptor, input_progress, before_flag); } // https://drafts.csswg.org/css-easing/#linear-canonicalization diff --git a/Libraries/LibWeb/CSS/GridTrackPlacement.h b/Libraries/LibWeb/CSS/GridTrackPlacement.h index eab906c1617a4..c2293fb439e25 100644 --- a/Libraries/LibWeb/CSS/GridTrackPlacement.h +++ b/Libraries/LibWeb/CSS/GridTrackPlacement.h @@ -61,31 +61,23 @@ class GridTrackPlacement { GridTrackPlacement absolutized(ComputationContext const&) const; - bool is_computationally_independent() const - { - return m_value.visit([](auto const& value) { return value.is_computationally_independent(); }); - } - bool operator==(GridTrackPlacement const& other) const = default; private: struct Auto { bool operator==(Auto const&) const = default; - bool is_computationally_independent() const { return true; } }; struct AreaOrLine { ValueComparingRefPtr line_number; Optional name; bool operator==(AreaOrLine const& other) const = default; - bool is_computationally_independent() const { return !line_number || line_number->is_computationally_independent(); } }; struct Span { ValueComparingNonnullRefPtr value; Optional name; bool operator==(Span const& other) const = default; - bool is_computationally_independent() const { return value->is_computationally_independent(); } }; GridTrackPlacement() diff --git a/Libraries/LibWeb/CSS/GridTrackSize.cpp b/Libraries/LibWeb/CSS/GridTrackSize.cpp index 42f6031deffe9..265fc4898e80a 100644 --- a/Libraries/LibWeb/CSS/GridTrackSize.cpp +++ b/Libraries/LibWeb/CSS/GridTrackSize.cpp @@ -339,13 +339,4 @@ GridTrackSizeList GridTrackSizeList::absolutized(ComputationContext const& conte return result; } -bool GridTrackSizeList::is_computationally_independent() const -{ - return all_of(m_list, [](auto const& item) { - return item.visit( - [](ExplicitGridTrack const& track) { return track.is_computationally_independent(); }, - [](GridLineNames const&) { return true; }); - }); -} - } diff --git a/Libraries/LibWeb/CSS/GridTrackSize.h b/Libraries/LibWeb/CSS/GridTrackSize.h index a208e45099bb7..34b216ef8c581 100644 --- a/Libraries/LibWeb/CSS/GridTrackSize.h +++ b/Libraries/LibWeb/CSS/GridTrackSize.h @@ -47,8 +47,6 @@ class GridSize { GridSize absolutized(ComputationContext const&) const; bool operator==(GridSize const& other) const = default; - bool is_computationally_independent() const { return m_value->is_computationally_independent(); } - private: ValueComparingNonnullRefPtr m_value; }; @@ -65,11 +63,6 @@ class GridMinMax { GridMinMax absolutized(ComputationContext const&) const; bool operator==(GridMinMax const& other) const = default; - bool is_computationally_independent() const - { - return m_min_grid_size.is_computationally_independent() && m_max_grid_size.is_computationally_independent(); - } - private: GridSize m_min_grid_size; GridSize m_max_grid_size; @@ -138,8 +131,6 @@ class GridTrackSizeList { GridTrackSizeList absolutized(ComputationContext const&) const; - bool is_computationally_independent() const; - private: bool m_is_subgrid { false }; bool m_preserve_line_name_sets { false }; @@ -179,8 +170,6 @@ class GridRepeat { GridRepeat absolutized(ComputationContext const&) const; bool operator==(GridRepeat const& other) const = default; - bool is_computationally_independent() const { return m_grid_track_size_list.is_computationally_independent() && (!m_repeat_count || m_repeat_count->is_computationally_independent()); } - private: GridRepeatType m_type; GridTrackSizeList m_grid_track_size_list; @@ -205,11 +194,6 @@ class ExplicitGridTrack { ExplicitGridTrack absolutized(ComputationContext const&) const; bool operator==(ExplicitGridTrack const& other) const = default; - bool is_computationally_independent() const - { - return m_value.visit([](auto const& value) { return value.is_computationally_independent(); }); - } - private: Variant m_value; }; diff --git a/Libraries/LibWeb/CSS/Interpolation.cpp b/Libraries/LibWeb/CSS/Interpolation.cpp deleted file mode 100644 index fc33491276893..0000000000000 --- a/Libraries/LibWeb/CSS/Interpolation.cpp +++ /dev/null @@ -1,2601 +0,0 @@ -/* - * Copyright (c) 2018-2023, Andreas Kling - * Copyright (c) 2021, the SerenityOS developers. - * Copyright (c) 2021-2025, Sam Atkins - * Copyright (c) 2024, Matthew Olsson - * Copyright (c) 2025-2026, Tim Ledbetter - * Copyright (c) 2025, Jelle Raaijmakers - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "Interpolation.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Web::CSS { - -template -static T interpolate_raw(T from, T to, float delta, Optional accepted_range = {}) -{ - if constexpr (AK::Detail::IsSame) { - if (accepted_range.has_value()) - return clamp(from + (to - from) * static_cast(delta), accepted_range->min, accepted_range->max); - return from + (to - from) * static_cast(delta); - } else if constexpr (AK::Detail::IsIntegral) { - auto from_float = static_cast(from); - auto to_float = static_cast(to); - auto min = accepted_range.has_value() ? accepted_range->min : NumericLimits::min(); - auto max = accepted_range.has_value() ? accepted_range->max : NumericLimits::max(); - auto unclamped_result = roundf(from_float + (to_float - from_float) * delta); - return static_cast>(clamp(unclamped_result, min, max)); - } - VERIFY(!accepted_range.has_value()); - return static_cast>(from + (to - from) * delta); -} - -static NonnullRefPtr with_keyword_values_resolved(DOM::Element& element, PropertyID property_id, StyleValue const& value) -{ - if (value.is_guaranteed_invalid()) { - // At the moment, we're only dealing with "real" properties, so this behaves the same as `unset`. - // https://drafts.csswg.org/css-values-5/#invalid-at-computed-value-time - return property_initial_value(property_id); - } - - if (!value.is_keyword()) - return value; - switch (value.as_keyword().keyword()) { - case Keyword::Initial: - case Keyword::Unset: - return property_initial_value(property_id); - case Keyword::Inherit: - return StyleComputer::get_non_animated_inherit_value(property_id, { element }); - default: - break; - } - return value; -} - -static RefPtr interpolate_discrete(StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete allow_discrete) -{ - if (from.equals(to)) - return from; - if (allow_discrete == AllowDiscrete::No) - return {}; - return delta >= 0.5f ? to : from; -} - -static RefPtr interpolate_scale(StyleValue const& a_from, StyleValue const& a_to, float delta) -{ - if (a_from.to_keyword() == Keyword::None && a_to.to_keyword() == Keyword::None) - return a_from; - - static auto const& one = TransformationStyleValue::create(PropertyID::Scale, TransformFunction::Scale, { NumberStyleValue::create(1), NumberStyleValue::create(1) }).leak_ref(); - - auto const& from = a_from.to_keyword() == Keyword::None ? one : a_from; - auto const& to = a_to.to_keyword() == Keyword::None ? one : a_to; - - auto const& from_transform = from.as_transformation(); - auto const& to_transform = to.as_transformation(); - auto from_values = from_transform.values(); - auto to_values = to_transform.values(); - - auto interpolated_x = interpolate_raw(number_from_style_value(from_values[0], 1), number_from_style_value(to_values[0], 1), delta, infinite_range); - auto interpolated_y = interpolate_raw(number_from_style_value(from_values[1], 1), number_from_style_value(to_values[1], 1), delta, infinite_range); - Optional interpolated_z; - - if (from_values.size() == 3 || to_values.size() == 3) { - static auto const& one_value = NumberStyleValue::create(1).leak_ref(); - auto from = from_values.size() == 3 ? from_values[2] : ValueComparingNonnullRefPtr { one_value }; - auto to = to_values.size() == 3 ? to_values[2] : ValueComparingNonnullRefPtr { one_value }; - interpolated_z = interpolate_raw(number_from_style_value(from, 1), number_from_style_value(to, 1), delta, infinite_range); - } - - StyleValueVector new_values = { NumberStyleValue::create(interpolated_x), NumberStyleValue::create(interpolated_y) }; - if (interpolated_z.has_value()) - new_values.append(NumberStyleValue::create(interpolated_z.value())); - - return TransformationStyleValue::create( - PropertyID::Scale, - new_values.size() == 3 ? TransformFunction::Scale3d : TransformFunction::Scale, - move(new_values)); -} - -// https://drafts.fxtf.org/filter-effects/#interpolation-of-filter-functions -static RefPtr interpolate_filter_function(DOM::Element& element, CalculationContext const& calculation_context, FilterStyleValue const& from, FilterStyleValue const& to, float delta, AllowDiscrete allow_discrete) -{ - VERIFY(!from.contains_url()); - VERIFY(!to.contains_url()); - - if (from.kind() != to.kind()) - return {}; - - switch (from.kind()) { - case FilterStyleValue::Kind::Blur: { - auto const& from_value = static_cast(from); - auto const& to_value = static_cast(to); - - CalculationContext blur_calculation_context = calculation_context; - blur_calculation_context.accepted_ranges_by_type.set(ValueType::Length, { 0, NumericLimits::max() }); - if (auto interpolated_style_value = interpolate_value(element, blur_calculation_context, from_value.radius(), to_value.radius(), delta, allow_discrete)) - return BlurFilterStyleValue::create(interpolated_style_value.release_nonnull()); - return {}; - } - case FilterStyleValue::Kind::HueRotate: { - auto const& from_value = static_cast(from); - auto const& to_value = static_cast(to); - if (auto interpolated_style_value = interpolate_value(element, calculation_context, from_value.angle(), to_value.angle(), delta, allow_discrete)) - return HueRotateFilterStyleValue::create(interpolated_style_value.release_nonnull()); - return {}; - } - case FilterStyleValue::Kind::Color: { - auto const& from_value = static_cast(from); - auto const& to_value = static_cast(to); - if (from_value.operation() != to_value.operation()) - return {}; - auto operation = from_value.operation(); - - CalculationContext filter_function_calculation_context = calculation_context; - switch (operation) { - case Gfx::ColorFilterType::Grayscale: - case Gfx::ColorFilterType::Invert: - case Gfx::ColorFilterType::Opacity: - case Gfx::ColorFilterType::Sepia: - filter_function_calculation_context.accepted_ranges_by_type.set(ValueType::Number, { 0, 1 }); - break; - case Gfx::ColorFilterType::Brightness: - case Gfx::ColorFilterType::Contrast: - case Gfx::ColorFilterType::Saturate: - filter_function_calculation_context.accepted_ranges_by_type.set(ValueType::Number, { 0, NumericLimits::max() }); - break; - } - - if (auto interpolated_style_value = interpolate_value(element, filter_function_calculation_context, from_value.amount(), to_value.amount(), delta, allow_discrete)) - return ColorFilterStyleValue::create(operation, *interpolated_style_value); - return {}; - } - case FilterStyleValue::Kind::DropShadow: { - auto const& from_value = static_cast(from); - auto const& to_value = static_cast(to); - - StyleValueVector from_shadows { from_value.shadow_style_value() }; - StyleValueVector to_shadows { to_value.shadow_style_value() }; - auto from_list = StyleValueList::create(move(from_shadows), StyleValueList::Separator::Comma); - auto to_list = StyleValueList::create(move(to_shadows), StyleValueList::Separator::Comma); - - auto result = interpolate_box_shadow(element, calculation_context, *from_list, *to_list, delta, allow_discrete); - if (!result) - return {}; - - auto const& result_shadow = result->as_value_list().value_at(0, false)->as_shadow(); - - RefPtr result_radius; - auto radius_has_value = delta >= 0.5f ? to_value.radius() : from_value.radius(); - if (radius_has_value) - result_radius = result_shadow.blur_radius(); - - return DropShadowFilterStyleValue::create( - result_shadow.offset_x(), - result_shadow.offset_y(), - result_radius, - result_shadow.color_or_null()); - } - } - VERIFY_NOT_REACHED(); -} - -static bool contains_url(StyleValueList const& list) -{ - return any_of(list.values(), [](auto& it) { return it->is_url(); }); -} - -// https://drafts.fxtf.org/filter-effects/#interpolation-of-filters -static RefPtr interpolate_filter_value_list(DOM::Element& element, CalculationContext const& calculation_context, StyleValue const& a_from, StyleValue const& a_to, float delta, AllowDiscrete allow_discrete) -{ - auto is_interpolable_filter_list = [](StyleValue const& value) { - if (!is_filter_style_value_list(value)) - return false; - return !contains_url(value.as_value_list()); - }; - - auto make_filter_value_list = [](StyleValueVector values) { - return StyleValueList::create(move(values), StyleValueList::Separator::Space, StyleValueList::Collapsible::No); - }; - - auto interpolate_filter_values = [&](StyleValueList const& from, StyleValueList const& to) -> RefPtr { - StyleValueVector interpolated_filter_values; - auto from_values = from.values(); - auto to_values = to.values(); - for (size_t i = 0; i < from.size(); ++i) { - auto const& from_value = from_values[i]->as_filter(); - auto const& to_value = to_values[i]->as_filter(); - - auto interpolated_value = interpolate_filter_function(element, calculation_context, from_value, to_value, delta, allow_discrete); - if (!interpolated_value) - return {}; - interpolated_filter_values.append(interpolated_value.release_nonnull()); - } - return make_filter_value_list(move(interpolated_filter_values)); - }; - - if (is_interpolable_filter_list(a_from) && is_interpolable_filter_list(a_to)) { - auto const& from_list = a_from.as_value_list(); - auto const& to_list = a_to.as_value_list(); - // If both filters have a of same length without and for each for which there is a corresponding item in each list - if (from_list.size() == to_list.size()) { - // Interpolate each pair following the rules in section Interpolation of Filter Functions. - return interpolate_filter_values(from_list, to_list); - } - - // If both filters have a of different length without and for each for which there is a corresponding item in each list - - // 1. Append the missing equivalent s from the longer list to the end of the shorter list. The new added s must be initialized to their initial values for interpolation. - auto append_missing_values_to = [&](StyleValueList const& short_list, StyleValueList const& longer_list) -> ValueComparingNonnullRefPtr { - StyleValueVector new_filter_list { short_list.values() }; - for (size_t i = new_filter_list.size(); i < longer_list.size(); ++i) - new_filter_list.append(FilterStyleValue::initial_value_for(longer_list.values()[i]->as_filter(), true)); - return make_filter_value_list(move(new_filter_list)); - }; - ValueComparingNonnullRefPtr from = from_list.size() < to_list.size() ? append_missing_values_to(from_list, to_list) : a_from; - ValueComparingNonnullRefPtr to = to_list.size() < from_list.size() ? append_missing_values_to(to_list, from_list) : a_to; - - // 2. Interpolate each pair following the rules in section Interpolation of Filter Functions. - return interpolate_filter_values(from->as_value_list(), to->as_value_list()); - } - - // If one filter is none and the other is a without - if ((is_interpolable_filter_list(a_from) && a_to.to_keyword() == Keyword::None) - || (is_interpolable_filter_list(a_to) && a_from.to_keyword() == Keyword::None)) { - - // 1. Replace none with the corresponding of the other filter. The new s must be initialized to their initial values for interpolation. - auto replace_none_with_initial_filter_list_values = [&](StyleValueList const& filter_value_list) { - StyleValueVector initial_values; - for (auto const& filter_value : filter_value_list.values()) { - // FIXME: We shouldn't apply the default color here. - initial_values.append(FilterStyleValue::initial_value_for(filter_value->as_filter(), true)); - } - return make_filter_value_list(move(initial_values)); - }; - - ValueComparingNonnullRefPtr from = a_from.is_keyword() ? replace_none_with_initial_filter_list_values(a_to.as_value_list()) : a_from; - ValueComparingNonnullRefPtr to = a_to.is_keyword() ? replace_none_with_initial_filter_list_values(a_from.as_value_list()) : a_to; - - // 2. Interpolate each pair following the rules in section Interpolation of Filter Functions. - return interpolate_filter_values(from->as_value_list(), to->as_value_list()); - } - - // Otherwise: - // Use discrete interpolation - return {}; -} - -static RefPtr interpolate_translate(DOM::Element& element, CalculationContext const& calculation_context, StyleValue const& a_from, StyleValue const& a_to, float delta, AllowDiscrete allow_discrete) -{ - if (a_from.to_keyword() == Keyword::None && a_to.to_keyword() == Keyword::None) - return a_from; - - static auto const& zero_px = LengthStyleValue::create(Length::make_px(0)).leak_ref(); - static auto const& zero = TransformationStyleValue::create(PropertyID::Translate, TransformFunction::Translate, { zero_px, zero_px }).leak_ref(); - - auto const& from = a_from.to_keyword() == Keyword::None ? zero : a_from; - auto const& to = a_to.to_keyword() == Keyword::None ? zero : a_to; - - auto const& from_transform = from.as_transformation(); - auto const& to_transform = to.as_transformation(); - auto from_values = from_transform.values(); - auto to_values = to_transform.values(); - - auto interpolated_x = interpolate_value(element, calculation_context, from_values[0], to_values[0], delta, allow_discrete); - if (!interpolated_x) - return {}; - auto interpolated_y = interpolate_value(element, calculation_context, from_values[1], to_values[1], delta, allow_discrete); - if (!interpolated_y) - return {}; - - RefPtr interpolated_z; - - if (from_values.size() == 3 || to_values.size() == 3) { - auto from_z = from_values.size() == 3 ? from_values[2] : zero_px; - auto to_z = to_values.size() == 3 ? to_values[2] : zero_px; - interpolated_z = interpolate_value(element, calculation_context, from_z, to_z, delta, allow_discrete); - if (!interpolated_z) - return {}; - } - - StyleValueVector new_values = { *interpolated_x, *interpolated_y }; - if (interpolated_z) - new_values.append(*interpolated_z); - - return TransformationStyleValue::create( - PropertyID::Translate, - new_values.size() == 3 ? TransformFunction::Translate3d : TransformFunction::Translate, - move(new_values)); -} - -// https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values -static FloatVector4 slerp(FloatVector4 const& from, FloatVector4 const& to, float delta) -{ - auto product = from.dot(to); - - product = clamp(product, -1.0f, 1.0f); - if (fabsf(product) >= 1.0f) - return from; - - auto theta = acosf(product); - auto w = sinf(delta * theta) / sqrtf(1 - (product * product)); - auto from_multiplier = cosf(delta * theta) - (product * w); - - if (abs(w) < AK::NumericLimits::epsilon()) - return from * from_multiplier; - - if (abs(from_multiplier) < AK::NumericLimits::epsilon()) - return to * w; - - return from * from_multiplier + to * w; -} - -static RefPtr interpolate_rotate(DOM::Element& element, CalculationContext const& calculation_context, StyleValue const& a_from, StyleValue const& a_to, float delta, AllowDiscrete allow_discrete) -{ - if (a_from.to_keyword() == Keyword::None && a_to.to_keyword() == Keyword::None) - return a_from; - - static auto const& zero_degrees_value = AngleStyleValue::create(Angle::make_degrees(0)).leak_ref(); - static auto const& zero = TransformationStyleValue::create(PropertyID::Rotate, TransformFunction::Rotate, { zero_degrees_value }).leak_ref(); - - auto const& from = a_from.to_keyword() == Keyword::None ? zero : a_from; - auto const& to = a_to.to_keyword() == Keyword::None ? zero : a_to; - - auto const& from_transform = from.as_transformation(); - auto const& to_transform = to.as_transformation(); - - auto from_transform_type = from_transform.transform_function(); - auto to_transform_type = to_transform.transform_function(); - auto from_values = from_transform.values(); - auto to_values = to_transform.values(); - - if (from_transform_type == to_transform_type && from_values.size() == 1) { - auto interpolated_angle = interpolate_value(element, calculation_context, from_values[0], to_values[0], delta, allow_discrete); - if (!interpolated_angle) - return {}; - return TransformationStyleValue::create(PropertyID::Rotate, from_transform_type, { *interpolated_angle.release_nonnull() }); - } - - FloatVector3 from_axis { 0, 0, 1 }; - auto from_angle_value = from_values[0]; - if (from_values.size() == 4) { - from_axis.set_x(from_values[0]->as_number().number()); - from_axis.set_y(from_values[1]->as_number().number()); - from_axis.set_z(from_values[2]->as_number().number()); - from_angle_value = from_values[3]; - } - float from_angle = Angle::from_style_value(from_angle_value, {}).to_radians(); - - FloatVector3 to_axis { 0, 0, 1 }; - auto to_angle_value = to_values[0]; - if (to_values.size() == 4) { - to_axis.set_x(to_values[0]->as_number().number()); - to_axis.set_y(to_values[1]->as_number().number()); - to_axis.set_z(to_values[2]->as_number().number()); - to_angle_value = to_values[3]; - } - float to_angle = Angle::from_style_value(to_angle_value, {}).to_radians(); - - auto from_axis_angle = [](FloatVector3 const& axis, float angle) -> FloatVector4 { - auto normalized = axis.normalized(); - auto half_angle = angle / 2.0f; - auto sin_half_angle = sinf(half_angle); - FloatVector4 result { normalized.x() * sin_half_angle, normalized.y() * sin_half_angle, normalized.z() * sin_half_angle, cosf(half_angle) }; - return result; - }; - - struct AxisAngle { - FloatVector3 axis; - float angle; - }; - auto quaternion_to_axis_angle = [](FloatVector4 const& quaternion) { - FloatVector3 axis { quaternion[0], quaternion[1], quaternion[2] }; - auto epsilon = 1e-5f; - auto sin_half_angle = sqrtf(max(0.0f, 1.0f - quaternion[3] * quaternion[3])); - auto angle = 2.0f * acosf(clamp(quaternion[3], -1.0f, 1.0f)); - if (sin_half_angle < epsilon) - return AxisAngle { axis, angle }; - axis = axis * (1.0f / sin_half_angle); - return AxisAngle { axis, angle }; - }; - - // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions - // If the normalized vectors are equal, or if one of the angles is zero, interpolate the angle - // numerically and use the rotation vector of the non-zero angle (or (0, 0, 1) if both are zero). - auto epsilon = 1e-5f; - auto from_axis_normalized = from_axis.length() > epsilon ? from_axis.normalized() : FloatVector3 { 0, 0, 1 }; - auto to_axis_normalized = to_axis.length() > epsilon ? to_axis.normalized() : FloatVector3 { 0, 0, 1 }; - bool axes_are_equal = (from_axis_normalized - to_axis_normalized).length() < epsilon; - if (axes_are_equal || from_angle == 0.f || to_angle == 0.f) { - auto result_angle = from_angle + (to_angle - from_angle) * delta; - FloatVector3 result_axis = { 0, 0, 1 }; - if (to_angle != 0.f) - result_axis = to_axis_normalized; - else if (from_angle != 0.f) - result_axis = from_axis_normalized; - - auto interpolated_x_axis = NumberStyleValue::create(result_axis.x()); - auto interpolated_y_axis = NumberStyleValue::create(result_axis.y()); - auto interpolated_z_axis = NumberStyleValue::create(result_axis.z()); - auto interpolated_angle = AngleStyleValue::create(Angle::make_degrees(AK::to_degrees(result_angle))); - - return TransformationStyleValue::create( - PropertyID::Rotate, - TransformFunction::Rotate3d, - { interpolated_x_axis, interpolated_y_axis, interpolated_z_axis, interpolated_angle }); - } - - // If the normalized vectors are not equal and both rotation angles are non-zero, convert to - // 4x4 matrices and interpolate as defined in Interpolation of Matrices. - auto from_quaternion = from_axis_angle(from_axis, from_angle); - auto to_quaternion = from_axis_angle(to_axis, to_angle); - - auto interpolated_quaternion = slerp(from_quaternion, to_quaternion, delta); - auto interpolated_axis_angle = quaternion_to_axis_angle(interpolated_quaternion); - auto interpolated_x_axis = NumberStyleValue::create(interpolated_axis_angle.axis.x()); - auto interpolated_y_axis = NumberStyleValue::create(interpolated_axis_angle.axis.y()); - auto interpolated_z_axis = NumberStyleValue::create(interpolated_axis_angle.axis.z()); - auto interpolated_angle = AngleStyleValue::create(Angle::make_degrees(AK::to_degrees(interpolated_axis_angle.angle))); - - return TransformationStyleValue::create( - PropertyID::Rotate, - TransformFunction::Rotate3d, - { interpolated_x_axis, interpolated_y_axis, interpolated_z_axis, interpolated_angle }); -} - -struct ExpandedGridTracksAndLines { - Vector tracks; - Vector> line_names; -}; - -static ExpandedGridTracksAndLines expand_grid_tracks_and_lines(GridTrackSizeList const& list) -{ - ExpandedGridTracksAndLines result; - Optional current_track; - Optional current_line_names; - auto append_result = [&] { - result.tracks.append(*current_track); - result.line_names.append(move(current_line_names)); - current_track.clear(); - current_line_names.clear(); - }; - - for (auto const& component : list.list()) { - if (auto const* grid_line_names = component.get_pointer()) { - VERIFY(!current_line_names.has_value()); - current_line_names = *grid_line_names; - } else if (auto const* grid_track = component.get_pointer()) { - if (current_track.has_value()) - append_result(); - - current_track = *grid_track; - } - if (current_track.has_value() && current_line_names.has_value()) - append_result(); - } - if (current_track.has_value()) - append_result(); - - return result; -} - -static void append_grid_track_with_line_names(GridTrackSizeList& list, ExplicitGridTrack track, Optional line_names) -{ - list.append(move(track)); - if (line_names.has_value()) - list.append(line_names.release_value()); -} - -static Optional interpolate_grid_track_size_list(DOM::Element& element, CalculationContext const& calculation_context, GridTrackSizeList const& from, GridTrackSizeList const& to, float delta) -{ - // https://drafts.csswg.org/css-grid-2/#track-sizing - // Animation type: if the list lengths match, by computed value type per item in the computed track list; - // discrete otherwise. - // - // https://drafts.csswg.org/css-grid-2/#computed-track-list-subgrid - // The computed track list of a subgrid axis is the subgrid keyword followed by a list of line names. - if (from.is_subgrid() || to.is_subgrid()) - return {}; - - auto interpolate_grid_size = [&](GridSize const& from_grid_size, GridSize const& to_grid_size) -> GridSize { - return GridSize { *interpolate_value(element, calculation_context, from_grid_size.style_value(), to_grid_size.style_value(), delta, AllowDiscrete::Yes) }; - }; - - auto expanded_from = expand_grid_tracks_and_lines(from); - auto expanded_to = expand_grid_tracks_and_lines(to); - - if (expanded_from.tracks.size() != expanded_to.tracks.size()) - return {}; - - GridTrackSizeList result; - for (size_t i = 0; i < expanded_from.tracks.size(); ++i) { - auto& from_track = expanded_from.tracks[i]; - auto& to_track = expanded_to.tracks[i]; - auto interpolated_line_names = delta < 0.5f ? move(expanded_from.line_names[i]) : move(expanded_to.line_names[i]); - - if (from_track.is_repeat() || to_track.is_repeat()) { - // https://drafts.csswg.org/css-grid/#repeat-interpolation - if (!from_track.is_repeat() || !to_track.is_repeat()) - return {}; - - auto from_repeat = from_track.repeat(); - auto to_repeat = to_track.repeat(); - if (!from_repeat.is_fixed() || !to_repeat.is_fixed()) - return {}; - if (from_repeat.repeat_count() != to_repeat.repeat_count() || from_repeat.grid_track_size_list().track_list().size() != to_repeat.grid_track_size_list().track_list().size()) - return {}; - - auto interpolated_repeat_grid_tracks = interpolate_grid_track_size_list(element, calculation_context, from_repeat.grid_track_size_list(), to_repeat.grid_track_size_list(), delta); - if (!interpolated_repeat_grid_tracks.has_value()) - return {}; - - ExplicitGridTrack interpolated_grid_track { GridRepeat { from_repeat.type(), move(*interpolated_repeat_grid_tracks), IntegerStyleValue::create(from_repeat.repeat_count()) } }; - append_grid_track_with_line_names(result, move(interpolated_grid_track), move(interpolated_line_names)); - } else if (from_track.is_minmax() && to_track.is_minmax()) { - auto from_minmax = from_track.minmax(); - auto to_minmax = to_track.minmax(); - auto interpolated_min = interpolate_grid_size(from_minmax.min_grid_size(), to_minmax.min_grid_size()); - auto interpolated_max = interpolate_grid_size(from_minmax.max_grid_size(), to_minmax.max_grid_size()); - ExplicitGridTrack interpolated_grid_track { GridMinMax { interpolated_min, interpolated_max } }; - append_grid_track_with_line_names(result, move(interpolated_grid_track), move(interpolated_line_names)); - } else if (from_track.is_default() && to_track.is_default()) { - auto const& from_grid_size = from_track.grid_size(); - auto const& to_grid_size = to_track.grid_size(); - auto interpolated_grid_size = interpolate_grid_size(from_grid_size, to_grid_size); - ExplicitGridTrack interpolated_grid_track { move(interpolated_grid_size) }; - append_grid_track_with_line_names(result, move(interpolated_grid_track), move(interpolated_line_names)); - } else { - auto interpolated_grid_track = delta < 0.5f ? move(from_track) : move(to_track); - append_grid_track_with_line_names(result, move(interpolated_grid_track), move(interpolated_line_names)); - } - } - return result; -} - -ValueComparingRefPtr interpolate_property(DOM::Element& element, PropertyID property_id, StyleValue const& a_from, StyleValue const& a_to, float delta, AllowDiscrete allow_discrete, ColorResolutionContext const* color_resolution_context) -{ - auto from = with_keyword_values_resolved(element, property_id, a_from); - auto to = with_keyword_values_resolved(element, property_id, a_to); - - auto calculation_context = CalculationContext::for_property(PropertyNameAndID::from_id(property_id)); - - auto animation_type = animation_type_from_longhand_property(property_id); - switch (animation_type) { - case AnimationType::ByComputedValue: - return interpolate_value(element, calculation_context, from, to, delta, allow_discrete, color_resolution_context); - case AnimationType::None: - return to; - case AnimationType::RepeatableList: - return interpolate_repeatable_list(element, calculation_context, from, to, delta, allow_discrete, color_resolution_context); - case AnimationType::Custom: { - if (property_id == PropertyID::Transform) { - if (auto interpolated_transform = interpolate_transform(element, calculation_context, from, to, delta, allow_discrete)) - return *interpolated_transform; - - // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms - // In some cases, an animation might cause a transformation matrix to be singular or non-invertible. - // For example, an animation in which scale moves from 1 to -1. At the time when the matrix is in - // such a state, the transformed element is not rendered. - return {}; - } - if (property_id == PropertyID::BoxShadow || property_id == PropertyID::TextShadow) { - if (auto interpolated_box_shadow = interpolate_box_shadow(element, calculation_context, from, to, delta, allow_discrete)) - return *interpolated_box_shadow; - return interpolate_discrete(from, to, delta, allow_discrete); - } - - if (property_id == PropertyID::FontStyle) { - static auto const& oblique_0deg_value = FontStyleStyleValue::create(FontStyleKeyword::Oblique, AngleStyleValue::create(Angle::make_degrees(0))).leak_ref(); - auto from_value = from->as_font_style().font_style() == FontStyleKeyword::Normal ? ValueComparingNonnullRefPtr { oblique_0deg_value } : from; - auto to_value = to->as_font_style().font_style() == FontStyleKeyword::Normal ? ValueComparingNonnullRefPtr { oblique_0deg_value } : to; - return interpolate_value(element, calculation_context, from_value, to_value, delta, allow_discrete, color_resolution_context); - } - - if (property_id == PropertyID::FontVariationSettings) { - // https://drafts.csswg.org/css-fonts/#font-variation-settings-def - // Two declarations of font-feature-settings can be animated between if they are "like". "Like" declarations - // are ones where the same set of properties appear (in any order). Because successive duplicate properties - // are applied instead of prior duplicate properties, two declarations can be "like" even if they have - // differing number of properties. If two declarations are "like" then animation occurs pairwise between - // corresponding values in the declarations. Otherwise, animation is not possible. - if (!from->is_value_list() || !to->is_value_list()) - return interpolate_discrete(from, to, delta, allow_discrete); - - // The values in these lists have already been deduplicated and sorted at this point, so we can use - // interpolate_value() to interpolate them pairwise. - return interpolate_value(element, calculation_context, from, to, delta, allow_discrete, color_resolution_context); - } - - // https://drafts.csswg.org/web-animations-1/#animating-visibility - if (property_id == PropertyID::Visibility) { - // For the visibility property, visible is interpolated as a discrete step where values of p between 0 and 1 map to visible and other values of p map to the closer endpoint. - // If neither value is visible, then discrete animation is used. - if (from->equals(to)) - return from; - - auto from_is_visible = from->to_keyword() == Keyword::Visible; - auto to_is_visible = to->to_keyword() == Keyword::Visible; - - if (from_is_visible || to_is_visible) { - if (delta <= 0) - return from; - if (delta >= 1) - return to; - return KeywordStyleValue::create(Keyword::Visible); - } - - return interpolate_discrete(from, to, delta, allow_discrete); - } - - // https://drafts.csswg.org/css-contain/#content-visibility-animation - if (property_id == PropertyID::ContentVisibility) { - // In general, the content-visibility property’s animation type is discrete. - // However, similar to interpolation of visibility, during interpolation between hidden and any other content-visibility value, - // p values between 0 and 1 map to the non-hidden value. - if (from->equals(to)) - return from; - - auto from_is_hidden = from->to_keyword() == Keyword::Hidden; - auto to_is_hidden = to->to_keyword() == Keyword::Hidden; - - if (from_is_hidden || to_is_hidden) { - if (allow_discrete == AllowDiscrete::No) - return {}; - auto non_hidden_value = from_is_hidden ? to : from; - if (delta <= 0) - return from; - if (delta >= 1) - return to; - return non_hidden_value; - } - return interpolate_discrete(from, to, delta, allow_discrete); - } - - // https://drafts.csswg.org/css-display-4/#display-animation - if (property_id == PropertyID::Display) { - // In general, the display property’s animation type is discrete. However, similar to interpolation of - // visibility (see Web Animations §  Animation of visibility), during interpolation between none and any - // other display value, p values between 0 and 1 map to the non-none value. Additionally, the element is - // inert as long as its display value would compute to none when ignoring the Transitions and Animations - // cascade origins. - // FIXME: Implement the inertness portion of this. - - if (from->equals(to)) - return from; - - auto from_is_none = from->as_display().display().is_none(); - auto to_is_none = to->as_display().display().is_none(); - - if (from_is_none || to_is_none) { - if (allow_discrete == AllowDiscrete::No) - return {}; - auto non_none_value = from_is_none ? to : from; - if (delta <= 0) - return from; - if (delta >= 1) - return to; - return non_none_value; - } - - return interpolate_discrete(from, to, delta, allow_discrete); - } - - if (property_id == PropertyID::Scale) { - if (auto result = interpolate_scale(from, to, delta)) - return result; - return interpolate_discrete(from, to, delta, allow_discrete); - } - - if (property_id == PropertyID::Translate) { - if (auto result = interpolate_translate(element, calculation_context, from, to, delta, allow_discrete)) - return result; - return interpolate_discrete(from, to, delta, allow_discrete); - } - - if (property_id == PropertyID::Rotate) { - if (auto result = interpolate_rotate(element, calculation_context, from, to, delta, allow_discrete)) - return result; - return interpolate_discrete(from, to, delta, allow_discrete); - } - - if (property_id == PropertyID::Filter || property_id == PropertyID::BackdropFilter) { - if (auto result = interpolate_filter_value_list(element, calculation_context, from, to, delta, allow_discrete)) - return result; - return interpolate_discrete(from, to, delta, allow_discrete); - } - - if (property_id == PropertyID::GridTemplateRows || property_id == PropertyID::GridTemplateColumns) { - // https://drafts.csswg.org/css-grid/#track-sizing - // If the list lengths match, by computed value type per item in the computed track list. - auto from_list = from->as_grid_track_size_list().grid_track_size_list(); - auto to_list = to->as_grid_track_size_list().grid_track_size_list(); - - auto interpolated_grid_tack_size_list = interpolate_grid_track_size_list(element, calculation_context, from_list, to_list, delta); - if (!interpolated_grid_tack_size_list.has_value()) - return interpolate_discrete(from, to, delta, allow_discrete); - - return GridTrackSizeListStyleValue::create(interpolated_grid_tack_size_list.release_value()); - } - - if (property_id == PropertyID::StrokeDasharray) { - // https://svgwg.org/svg2-draft/painting.html#StrokeDashing - // If either start or end compute to none or are invalid, start or end are combined using the discrete animation type. - if (!from->is_value_list() || !to->is_value_list()) - return interpolate_discrete(from, to, delta, allow_discrete); - - // Otherwise, repeat both dash patterns of start and end value list until the length of elements in - // both value lists match. Each item is then combined by computed value. - if (auto result = interpolate_repeatable_list(element, calculation_context, from, to, delta, allow_discrete, color_resolution_context)) - return result.release_nonnull(); - return interpolate_discrete(from, to, delta, allow_discrete); - } - - // FIXME: Handle all custom animatable properties - [[fallthrough]]; - } - case AnimationType::Discrete: - default: - return interpolate_discrete(from, to, delta, allow_discrete); - } -} - -// https://drafts.csswg.org/css-transitions/#transitionable -bool property_values_are_transitionable(PropertyID property_id, StyleValue const& old_value, StyleValue const& new_value, DOM::Element& element, TransitionBehavior transition_behavior) -{ - // When comparing the before-change style and after-change style for a given property, - // the property values are transitionable if they have an animation type that is neither not animatable nor discrete. - - auto animation_type = animation_type_from_longhand_property(property_id); - if (animation_type == AnimationType::None || (transition_behavior != TransitionBehavior::AllowDiscrete && animation_type == AnimationType::Discrete)) - return false; - - // Even when a property is transitionable, the two values may not be. The spec uses the example of inset/non-inset shadows. - if (transition_behavior != TransitionBehavior::AllowDiscrete && !interpolate_property(element, property_id, old_value, new_value, 0.5f, AllowDiscrete::No)) - return false; - - return true; -} - -static Optional interpolate_matrices(FloatMatrix4x4 const& from, FloatMatrix4x4 const& to, float delta) -{ - struct DecomposedValues { - FloatVector3 translation; - FloatVector3 scale; - FloatVector3 skew; - FloatVector4 rotation; - FloatVector4 perspective; - }; - // https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix - static constexpr auto decompose = [](FloatMatrix4x4 matrix) -> Optional { - // https://drafts.csswg.org/css-transforms-1/#supporting-functions - static constexpr auto combine = [](auto a, auto b, float ascl, float bscl) { - return FloatVector3 { - ascl * a[0] + bscl * b[0], - ascl * a[1] + bscl * b[1], - ascl * a[2] + bscl * b[2], - }; - }; - - // Normalize the matrix. - if (matrix[3, 3] == 0.f) - return {}; - - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - matrix[i, j] /= matrix[3, 3]; - - // perspectiveMatrix is used to solve for perspective, but it also provides - // an easy way to test for singularity of the upper 3x3 component. - auto perspective_matrix = matrix; - for (int i = 0; i < 3; i++) - perspective_matrix[3, i] = 0.f; - perspective_matrix[3, 3] = 1.f; - - if (!perspective_matrix.is_invertible()) - return {}; - - DecomposedValues values; - - // First, isolate perspective. - if (matrix[3, 0] != 0.f || matrix[3, 1] != 0.f || matrix[3, 2] != 0.f) { - // rightHandSide is the right hand side of the equation. - // Note: It is the bottom side in a row-major matrix - FloatVector4 bottom_side = { - matrix[3, 0], - matrix[3, 1], - matrix[3, 2], - matrix[3, 3], - }; - - // Solve the equation by inverting perspectiveMatrix and multiplying - // rightHandSide by the inverse. - auto inverse_perspective_matrix = perspective_matrix.inverse(); - auto transposed_inverse_perspective_matrix = inverse_perspective_matrix.transpose(); - values.perspective = transposed_inverse_perspective_matrix * bottom_side; - } else { - // No perspective. - values.perspective = { 0.0, 0.0, 0.0, 1.0 }; - } - - // Next take care of translation - for (int i = 0; i < 3; i++) - values.translation[i] = matrix[i, 3]; - - // Now get scale and shear. 'row' is a 3 element array of 3 component vectors - FloatVector3 row[3]; - for (int i = 0; i < 3; i++) - row[i] = { matrix[0, i], matrix[1, i], matrix[2, i] }; - - // Compute X scale factor and normalize first row. - values.scale[0] = row[0].length(); - row[0].normalize(); - - // Compute XY shear factor and make 2nd row orthogonal to 1st. - values.skew[0] = row[0].dot(row[1]); - row[1] = combine(row[1], row[0], 1.f, -values.skew[0]); - - // Now, compute Y scale and normalize 2nd row. - values.scale[1] = row[1].length(); - row[1].normalize(); - values.skew[0] /= values.scale[1]; - - // Compute XZ and YZ shears, orthogonalize 3rd row - values.skew[1] = row[0].dot(row[2]); - row[2] = combine(row[2], row[0], 1.f, -values.skew[1]); - values.skew[2] = row[1].dot(row[2]); - row[2] = combine(row[2], row[1], 1.f, -values.skew[2]); - - // Next, get Z scale and normalize 3rd row. - values.scale[2] = row[2].length(); - row[2].normalize(); - values.skew[1] /= values.scale[2]; - values.skew[2] /= values.scale[2]; - - // At this point, the matrix (in rows) is orthonormal. - // Check for a coordinate system flip. If the determinant - // is -1, then negate the matrix and the scaling factors. - auto pdum3 = row[1].cross(row[2]); - if (row[0].dot(pdum3) < 0.f) { - for (int i = 0; i < 3; i++) { - values.scale[i] *= -1.f; - row[i][0] *= -1.f; - row[i][1] *= -1.f; - row[i][2] *= -1.f; - } - } - - // Now, get the rotations out - values.rotation[0] = 0.5f * sqrt(max(1.f + row[0][0] - row[1][1] - row[2][2], 0.f)); - values.rotation[1] = 0.5f * sqrt(max(1.f - row[0][0] + row[1][1] - row[2][2], 0.f)); - values.rotation[2] = 0.5f * sqrt(max(1.f - row[0][0] - row[1][1] + row[2][2], 0.f)); - values.rotation[3] = 0.5f * sqrt(max(1.f + row[0][0] + row[1][1] + row[2][2], 0.f)); - - if (row[2][1] > row[1][2]) - values.rotation[0] = -values.rotation[0]; - if (row[0][2] > row[2][0]) - values.rotation[1] = -values.rotation[1]; - if (row[1][0] > row[0][1]) - values.rotation[2] = -values.rotation[2]; - - // FIXME: This accounts for the fact that the browser coordinate system is left-handed instead of right-handed. - // The reason for this is that the positive Y-axis direction points down instead of up. To fix this, we - // invert the Y axis. However, it feels like the spec pseudo-code above should have taken something like - // this into account, so we're probably doing something else wrong. - values.rotation[2] *= -1; - - return values; - }; - - // https://drafts.csswg.org/css-transforms-2/#recomposing-to-a-3d-matrix - static constexpr auto recompose = [](DecomposedValues const& values) -> FloatMatrix4x4 { - auto matrix = FloatMatrix4x4::identity(); - - // apply perspective - for (int i = 0; i < 4; i++) - matrix[3, i] = values.perspective[i]; - - // apply translation - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 3; j++) - matrix[i, 3] += values.translation[j] * matrix[i, j]; - } - - // apply rotation - auto x = values.rotation[0]; - auto y = values.rotation[1]; - auto z = values.rotation[2]; - auto w = values.rotation[3]; - - // Construct a composite rotation matrix from the quaternion values - // rotationMatrix is a identity 4x4 matrix initially - auto rotation_matrix = FloatMatrix4x4::identity(); - rotation_matrix[0, 0] = 1.f - 2.f * (y * y + z * z); - rotation_matrix[1, 0] = 2.f * (x * y - z * w); - rotation_matrix[2, 0] = 2.f * (x * z + y * w); - rotation_matrix[0, 1] = 2.f * (x * y + z * w); - rotation_matrix[1, 1] = 1.f - 2.f * (x * x + z * z); - rotation_matrix[2, 1] = 2.f * (y * z - x * w); - rotation_matrix[0, 2] = 2.f * (x * z - y * w); - rotation_matrix[1, 2] = 2.f * (y * z + x * w); - rotation_matrix[2, 2] = 1.f - 2.f * (x * x + y * y); - - matrix = matrix * rotation_matrix; - - // apply skew - // temp is a identity 4x4 matrix initially - auto temp = FloatMatrix4x4::identity(); - if (values.skew[2] != 0.f) { - temp[1, 2] = values.skew[2]; - matrix = matrix * temp; - } - - if (values.skew[1] != 0.f) { - temp[1, 2] = 0.f; - temp[0, 2] = values.skew[1]; - matrix = matrix * temp; - } - - if (values.skew[0] != 0.f) { - temp[0, 2] = 0.f; - temp[0, 1] = values.skew[0]; - matrix = matrix * temp; - } - - // apply scale - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 4; j++) - matrix[j, i] *= values.scale[i]; - } - - return matrix; - }; - - // https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values - static constexpr auto interpolate = [](DecomposedValues& from, DecomposedValues& to, float delta) -> DecomposedValues { - auto interpolated_rotation = slerp(from.rotation, to.rotation, delta); - return { - interpolate_raw(from.translation, to.translation, delta), - interpolate_raw(from.scale, to.scale, delta), - interpolate_raw(from.skew, to.skew, delta), - interpolated_rotation, - interpolate_raw(from.perspective, to.perspective, delta), - }; - }; - - auto from_decomposed = decompose(from); - auto to_decomposed = decompose(to); - if (!from_decomposed.has_value() || !to_decomposed.has_value()) - return {}; - auto interpolated_decomposed = interpolate(from_decomposed.value(), to_decomposed.value(), delta); - return recompose(interpolated_decomposed); -} - -static StyleValueVector matrix_to_style_value_vector(FloatMatrix4x4 const& matrix) -{ - StyleValueVector values; - values.ensure_capacity(16); - for (int i = 0; i < 16; i++) - values.unchecked_append(NumberStyleValue::create(matrix[i % 4, i / 4])); - return values; -} - -// https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms -RefPtr interpolate_transform(DOM::Element& element, CalculationContext const& calculation_context, - StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete allow_discrete) -{ - // * If both Va and Vb are none: - // * Vresult is none. - if (from.is_keyword() && from.as_keyword().keyword() == Keyword::None - && to.is_keyword() && to.as_keyword().keyword() == Keyword::None) { - return KeywordStyleValue::create(Keyword::None); - } - - // * Treating none as a list of zero length, if Va or Vb differ in length: - auto style_value_to_transformations = [](StyleValue const& style_value) - -> Vector> { - if (style_value.is_transformation()) - return { style_value.as_transformation() }; - - // NB: This encompasses both the allowed value "none" and any invalid values. - if (!style_value.is_value_list()) - return {}; - - Vector> result; - result.ensure_capacity(style_value.as_value_list().size()); - for (auto const& value : style_value.as_value_list().values()) { - VERIFY(value->is_transformation()); - result.unchecked_append(value->as_transformation()); - } - return result; - }; - auto from_transformations = style_value_to_transformations(from); - auto to_transformations = style_value_to_transformations(to); - if (from_transformations.size() != to_transformations.size()) { - // * extend the shorter list to the length of the longer list, setting the function at each additional - // position to the identity transform function matching the function at the corresponding position in the - // longer list. Both transform function lists are then interpolated following the next rule. - auto& shorter_list = from_transformations.size() < to_transformations.size() ? from_transformations : to_transformations; - auto const& longer_list = from_transformations.size() < to_transformations.size() ? to_transformations : from_transformations; - for (size_t i = shorter_list.size(); i < longer_list.size(); ++i) { - auto const& transformation = longer_list[i]; - shorter_list.append(TransformationStyleValue::identity_transformation(transformation->transform_function())); - } - } - - // https://drafts.csswg.org/css-transforms-1/#transform-primitives - auto is_2d_primitive = [](TransformFunction function) { - return first_is_one_of(function, - TransformFunction::Rotate, - TransformFunction::Scale, - TransformFunction::Translate); - }; - auto is_2d_transform = [&is_2d_primitive](TransformFunction function) { - return is_2d_primitive(function) - || first_is_one_of(function, - TransformFunction::ScaleX, - TransformFunction::ScaleY, - TransformFunction::TranslateX, - TransformFunction::TranslateY); - }; - - // https://drafts.csswg.org/css-transforms-2/#transform-primitives - auto is_3d_primitive = [](TransformFunction function) { - return first_is_one_of(function, - TransformFunction::Rotate3d, - TransformFunction::Scale3d, - TransformFunction::Translate3d); - }; - auto is_3d_transform = [&is_2d_transform, &is_3d_primitive](TransformFunction function) { - return is_2d_transform(function) - || is_3d_primitive(function) - || first_is_one_of(function, - TransformFunction::RotateX, - TransformFunction::RotateY, - TransformFunction::RotateZ, - TransformFunction::ScaleZ, - TransformFunction::TranslateZ); - }; - - auto convert_2d_transform_to_primitive = [](NonnullRefPtr transform) - -> NonnullRefPtr { - TransformFunction generic_function; - StyleValueVector parameters; - auto values = transform->values(); - switch (transform->transform_function()) { - case TransformFunction::Scale: - generic_function = TransformFunction::Scale; - parameters.append(values[0]); - parameters.append(values.size() > 1 ? values[1] : values[0]); - break; - case TransformFunction::ScaleX: - generic_function = TransformFunction::Scale; - parameters.append(values[0]); - parameters.append(NumberStyleValue::create(1.)); - break; - case TransformFunction::ScaleY: - generic_function = TransformFunction::Scale; - parameters.append(NumberStyleValue::create(1.)); - parameters.append(values[0]); - break; - case TransformFunction::Rotate: - generic_function = TransformFunction::Rotate; - parameters.append(values[0]); - break; - case TransformFunction::Translate: - generic_function = TransformFunction::Translate; - parameters.append(values[0]); - parameters.append(values.size() > 1 - ? values[1] - : LengthStyleValue::create(Length::make_px(0.))); - break; - case TransformFunction::TranslateX: - generic_function = TransformFunction::Translate; - parameters.append(values[0]); - parameters.append(LengthStyleValue::create(Length::make_px(0.))); - break; - case TransformFunction::TranslateY: - generic_function = TransformFunction::Translate; - parameters.append(LengthStyleValue::create(Length::make_px(0.))); - parameters.append(values[0]); - break; - default: - VERIFY_NOT_REACHED(); - } - return TransformationStyleValue::create(PropertyID::Transform, generic_function, move(parameters)); - }; - - auto convert_3d_transform_to_primitive = [&](NonnullRefPtr transform) - -> NonnullRefPtr { - // NB: Convert to 2D primitive if possible so we don't have to deal with scale/translate X/Y separately. - if (is_2d_transform(transform->transform_function())) - transform = convert_2d_transform_to_primitive(transform); - - TransformFunction generic_function; - StyleValueVector parameters; - auto values = transform->values(); - switch (transform->transform_function()) { - case TransformFunction::Rotate: - case TransformFunction::RotateZ: - generic_function = TransformFunction::Rotate3d; - parameters.append(NumberStyleValue::create(0.)); - parameters.append(NumberStyleValue::create(0.)); - parameters.append(NumberStyleValue::create(1.)); - parameters.append(values[0]); - break; - case TransformFunction::RotateX: - generic_function = TransformFunction::Rotate3d; - parameters.append(NumberStyleValue::create(1.)); - parameters.append(NumberStyleValue::create(0.)); - parameters.append(NumberStyleValue::create(0.)); - parameters.append(values[0]); - break; - case TransformFunction::RotateY: - generic_function = TransformFunction::Rotate3d; - parameters.append(NumberStyleValue::create(0.)); - parameters.append(NumberStyleValue::create(1.)); - parameters.append(NumberStyleValue::create(0.)); - parameters.append(values[0]); - break; - case TransformFunction::Scale: - generic_function = TransformFunction::Scale3d; - parameters.append(values[0]); - parameters.append(values.size() > 1 ? values[1] : values[0]); - parameters.append(NumberStyleValue::create(1.)); - break; - case TransformFunction::ScaleZ: - generic_function = TransformFunction::Scale3d; - parameters.append(NumberStyleValue::create(1.)); - parameters.append(NumberStyleValue::create(1.)); - parameters.append(values[0]); - break; - case TransformFunction::Translate: - generic_function = TransformFunction::Translate3d; - parameters.append(values[0]); - parameters.append(values.size() > 1 - ? values[1] - : LengthStyleValue::create(Length::make_px(0.))); - parameters.append(LengthStyleValue::create(Length::make_px(0.))); - break; - case TransformFunction::TranslateZ: - generic_function = TransformFunction::Translate3d; - parameters.append(LengthStyleValue::create(Length::make_px(0.))); - parameters.append(LengthStyleValue::create(Length::make_px(0.))); - parameters.append(values[0]); - break; - default: - generic_function = TransformFunction::Matrix3d; - // NB: Called during animation interpolation. - auto paintable_box = [&] -> Optional { - if (auto box = element.unsafe_paintable_box()) - return *box; - return {}; - }(); - parameters = matrix_to_style_value_vector(transform->to_matrix(paintable_box)); - } - return TransformationStyleValue::create(PropertyID::Transform, generic_function, move(parameters)); - }; - - // * Let Vresult be an empty list. Beginning at the start of Va and Vb, compare the corresponding functions at each - // position: - StyleValueVector result; - result.ensure_capacity(from_transformations.size()); - size_t index = 0; - for (; index < from_transformations.size(); ++index) { - auto from_transformation = from_transformations[index]; - auto to_transformation = to_transformations[index]; - - auto from_function = from_transformation->transform_function(); - auto to_function = to_transformation->transform_function(); - - // * While the functions have either the same name, or are derivatives of the same primitive transform - // function, interpolate the corresponding pair of functions as described in § 10 Interpolation of - // primitives and derived transform functions and append the result to Vresult. - - // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions - // Two different types of transform functions that share the same primitive, or transform functions of the same - // type with different number of arguments can be interpolated. Both transform functions need a former - // conversion to the common primitive first and get interpolated numerically afterwards. The computed value will - // be the primitive with the resulting interpolated arguments. - - // The transform functions , matrix3d() and perspective() get converted into 4x4 matrices first and - // interpolated as defined in section Interpolation of Matrices afterwards. - if (first_is_one_of(TransformFunction::Matrix, from_function, to_function) - || first_is_one_of(TransformFunction::Matrix3d, from_function, to_function) - || first_is_one_of(TransformFunction::Perspective, from_function, to_function)) { - break; - } - - // If both transform functions share a primitive in the two-dimensional space, both transform functions get - // converted to the two-dimensional primitive. If one or both transform functions are three-dimensional - // transform functions, the common three-dimensional primitive is used. - if (is_2d_transform(from_function) && is_2d_transform(to_function)) { - from_transformation = convert_2d_transform_to_primitive(from_transformation); - to_transformation = convert_2d_transform_to_primitive(to_transformation); - } else if (is_3d_transform(from_function) || is_3d_transform(to_function)) { - // NB: 3D primitives do not support value expansion like their 2D counterparts do (e.g. scale(1.5) -> - // scale(1.5, 1.5), so we check if they are already a primitive first. - if (!is_3d_primitive(from_function)) - from_transformation = convert_3d_transform_to_primitive(from_transformation); - if (!is_3d_primitive(to_function)) - to_transformation = convert_3d_transform_to_primitive(to_transformation); - } - from_function = from_transformation->transform_function(); - to_function = to_transformation->transform_function(); - - // NB: We converted both functions to their primitives. But if they're different primitives or if they have a - // different number of values, we can't interpolate numerically between them. Break here so the next loop - // can take care of the remaining functions. - auto from_values = from_transformation->values(); - auto to_values = to_transformation->values(); - if (from_function != to_function || from_values.size() != to_values.size()) - break; - - // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions - if (from_function == TransformFunction::Rotate3d) { - // FIXME: For interpolations with the primitive rotate3d(), the direction vectors of the transform functions get - // normalized first. If the normalized vectors are not equal and both rotation angles are non-zero the - // transform functions get converted into 4x4 matrices first and interpolated as defined in section - // Interpolation of Matrices afterwards. Otherwise the rotation angle gets interpolated numerically and the - // rotation vector of the non-zero angle is used or (0, 0, 1) if both angles are zero. - - auto interpolated_rotation = interpolate_rotate(element, calculation_context, from_transformation, - to_transformation, delta, AllowDiscrete::No); - if (!interpolated_rotation) - break; - result.unchecked_append(*interpolated_rotation); - } else { - StyleValueVector interpolated; - interpolated.ensure_capacity(from_values.size()); - for (size_t i = 0; i < from_values.size(); ++i) { - auto interpolated_value = interpolate_value(element, calculation_context, from_values[i], to_values[i], - delta, AllowDiscrete::No); - if (!interpolated_value) - break; - interpolated.unchecked_append(*interpolated_value); - } - if (interpolated.size() != from_values.size()) - break; - result.unchecked_append(TransformationStyleValue::create(PropertyID::Transform, from_function, move(interpolated))); - } - } - - // NB: Return if we're done. - if (index == from_transformations.size()) - return StyleValueList::create(move(result), StyleValueList::Separator::Space); - - // * If the pair do not have a common name or primitive transform function, post-multiply the remaining - // transform functions in each of Va and Vb respectively to produce two 4x4 matrices. Interpolate these two - // matrices as described in § 11 Interpolation of Matrices, append the result to Vresult, and cease - // iterating over Va and Vb. - // NB: Called during animation interpolation. - Optional paintable_box; - auto paintable = element.unsafe_paintable(); - if (auto const* box = paintable.ptr()) - paintable_box = *box; - - auto post_multiply_remaining_transformations = [&paintable_box](size_t start_index, Vector> const& transformations) -> Optional { - FloatMatrix4x4 result = FloatMatrix4x4::identity(); - for (auto index = start_index; index < transformations.size(); ++index) { - auto const& transformation = transformations[index]; - if (!paintable_box.has_value() && !transformation->can_be_converted_to_matrix_without_reference_box()) - return {}; - result = result * transformation->to_matrix(paintable_box); - } - - return result; - }; - auto from_matrix = post_multiply_remaining_transformations(index, from_transformations); - auto to_matrix = post_multiply_remaining_transformations(index, to_transformations); - - // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms - // If one of the matrices for interpolation is non-invertible, the used animation function must - // fall-back to a discrete animation according to the rules of the respective animation specification. - if (!from_matrix.has_value() || !to_matrix.has_value()) - return interpolate_discrete(from, to, delta, allow_discrete); - - auto maybe_interpolated_matrix = interpolate_matrices(from_matrix.value(), to_matrix.value(), delta); - if (!maybe_interpolated_matrix.has_value()) - return interpolate_discrete(from, to, delta, allow_discrete); - - result.append(TransformationStyleValue::create(PropertyID::Transform, TransformFunction::Matrix3d, - matrix_to_style_value_vector(maybe_interpolated_matrix.release_value()))); - - return StyleValueList::create(move(result), StyleValueList::Separator::Space); -} - -RefPtr interpolate_box_shadow(DOM::Element& element, CalculationContext const& calculation_context, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete allow_discrete) -{ - // https://drafts.csswg.org/css-backgrounds/#box-shadow - // Animation type: by computed value, treating none as a zero-item list and appending blank shadows - // (transparent 0 0 0 0) with a corresponding inset keyword as needed to match the longer list if - // the shorter list is otherwise compatible with the longer one - - static constexpr auto process_list = [](StyleValue const& value) -> StyleValueVector { - if (value.to_keyword() == Keyword::None) - return {}; - - return StyleValueVector { value.as_value_list().values() }; - }; - - static constexpr auto extend_list_if_necessary = [](StyleValueVector& values, StyleValueVector const& other) { - values.ensure_capacity(other.size()); - for (size_t i = values.size(); i < other.size(); i++) { - values.unchecked_append(ShadowStyleValue::create( - other.get(0).value()->as_shadow().shadow_type(), - ColorStyleValue::create_from_color(Color::Transparent, ColorSyntax::Legacy), - LengthStyleValue::create(Length::make_px(0)), - LengthStyleValue::create(Length::make_px(0)), - LengthStyleValue::create(Length::make_px(0)), - LengthStyleValue::create(Length::make_px(0)), - other[i]->as_shadow().placement())); - } - }; - - StyleValueVector from_shadows = process_list(from); - StyleValueVector to_shadows = process_list(to); - - extend_list_if_necessary(from_shadows, to_shadows); - extend_list_if_necessary(to_shadows, from_shadows); - - VERIFY(from_shadows.size() == to_shadows.size()); - StyleValueVector result_shadows; - result_shadows.ensure_capacity(from_shadows.size()); - - // NB: Called during style interpolation. - ColorResolutionContext color_resolution_context {}; - if (auto* node = element.unsafe_layout_node()) - color_resolution_context = ColorResolutionContext::for_layout_node_with_style(*node); - - for (size_t i = 0; i < from_shadows.size(); i++) { - auto const& from_shadow = from_shadows[i]->as_shadow(); - auto const& to_shadow = to_shadows[i]->as_shadow(); - auto interpolated_offset_x = interpolate_value(element, calculation_context, from_shadow.offset_x(), to_shadow.offset_x(), delta, allow_discrete); - auto interpolated_offset_y = interpolate_value(element, calculation_context, from_shadow.offset_y(), to_shadow.offset_y(), delta, allow_discrete); - auto interpolated_blur_radius = interpolate_value(element, calculation_context, from_shadow.blur_radius(), to_shadow.blur_radius(), delta, allow_discrete); - auto interpolated_spread_distance = interpolate_value(element, calculation_context, from_shadow.spread_distance(), to_shadow.spread_distance(), delta, allow_discrete); - if (!interpolated_offset_x || !interpolated_offset_y || !interpolated_blur_radius || !interpolated_spread_distance) - return {}; - - auto interpolated_color_value = interpolate_color(*from_shadow.color(), *to_shadow.color(), delta, {}, color_resolution_context); - if (!interpolated_color_value) - interpolated_color_value = ColorStyleValue::create_from_color(Color::Black, ColorSyntax::Modern); - - auto result_shadow = ShadowStyleValue::create( - from_shadow.shadow_type(), - interpolated_color_value.release_nonnull(), - *interpolated_offset_x, - *interpolated_offset_y, - *interpolated_blur_radius, - *interpolated_spread_distance, - delta >= 0.5f ? to_shadow.placement() : from_shadow.placement()); - result_shadows.unchecked_append(result_shadow); - } - - return StyleValueList::create(move(result_shadows), StyleValueList::Separator::Comma); -} - -static Optional get_value_type_of_numeric_style_value(StyleValue const& value, CalculationContext const& calculation_context) -{ - switch (value.type()) { - case StyleValue::Type::Angle: - return ValueType::Angle; - case StyleValue::Type::Frequency: - return ValueType::Frequency; - case StyleValue::Type::Integer: - return ValueType::Integer; - case StyleValue::Type::Length: - return ValueType::Length; - case StyleValue::Type::Number: - return ValueType::Number; - case StyleValue::Type::Percentage: - return calculation_context.percentages_resolve_as.value_or(ValueType::Percentage); - case StyleValue::Type::Resolution: - return ValueType::Resolution; - case StyleValue::Type::Time: - return ValueType::Time; - case StyleValue::Type::Calculated: { - auto const& calculated = value.as_calculated(); - if (calculated.resolves_to_angle_percentage()) - return ValueType::Angle; - if (calculated.resolves_to_frequency_percentage()) - return ValueType::Frequency; - if (calculated.resolves_to_length_percentage()) - return ValueType::Length; - if (calculated.resolves_to_resolution()) - return ValueType::Resolution; - if (calculated.resolves_to_number()) - return calculation_context.resolve_numbers_as_integers ? ValueType::Integer : ValueType::Number; - if (calculated.resolves_to_percentage()) - return calculation_context.percentages_resolve_as.value_or(ValueType::Percentage); - if (calculated.resolves_to_time_percentage()) - return ValueType::Time; - - return {}; - } - default: - return {}; - } -} - -static RefPtr interpolate_mixed_value(CalculationContext const& calculation_context, StyleValue const& from, StyleValue const& to, float delta) -{ - auto from_value_type = get_value_type_of_numeric_style_value(from, calculation_context); - auto to_value_type = get_value_type_of_numeric_style_value(to, calculation_context); - - if (from_value_type.has_value() && from_value_type == to_value_type) { - // https://drafts.csswg.org/css-values-4/#combine-mixed - // The computed value of a percentage-dimension mix is defined as - // FIXME: a computed dimension if the percentage component is zero or is defined specifically to compute to a dimension value - // a computed percentage if the dimension component is zero - // a computed calc() expression otherwise - if (auto const* from_dimension_value = as_if(from); from_dimension_value && to.type() == StyleValue::Type::Percentage) { - auto dimension_component = from_dimension_value->raw_value() * (1.f - delta); - auto percentage_component = to.as_percentage().raw_value() * delta; - if (dimension_component == 0.f) - return PercentageStyleValue::create(Percentage { percentage_component }); - } else if (auto const* to_dimension_value = as_if(to); to_dimension_value && from.type() == StyleValue::Type::Percentage) { - auto dimension_component = to_dimension_value->raw_value() * delta; - auto percentage_component = from.as_percentage().raw_value() * (1.f - delta); - if (dimension_component == 0) - return PercentageStyleValue::create(Percentage { percentage_component }); - } - - // https://drafts.csswg.org/css-values-4/#combine-math - // Interpolation of math functions, with each other or with numeric values and other numeric-valued functions, is defined as Vresult = calc((1 - p) * VA + p * VB). - Vector from_contribution_factors; - from_contribution_factors.append(CalcNodeRef::from_style_value(from)); - from_contribution_factors.append(CalcNodeRef::numeric(Number { Number::Type::Number, 1.f - delta })); - auto from_contribution = CalcNodeRef::product(move(from_contribution_factors)); - - Vector to_contribution_factors; - to_contribution_factors.append(CalcNodeRef::from_style_value(to)); - to_contribution_factors.append(CalcNodeRef::numeric(Number { Number::Type::Number, delta })); - auto to_contribution = CalcNodeRef::product(move(to_contribution_factors)); - - Vector contributions; - contributions.append(move(from_contribution)); - contributions.append(move(to_contribution)); - auto interpolated_sum = CalcNodeRef::sum(move(contributions)); - - auto numeric_type = interpolated_sum.determine_type(calculation_context); - return CalculatedStyleValue::create( - simplify_a_calculation_tree(interpolated_sum, calculation_context, {}), - numeric_type.value(), - calculation_context); - } - - return {}; -} - -static RefPtr interpolate_value_impl(DOM::Element& element, CalculationContext const& calculation_context, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete allow_discrete, ColorResolutionContext const* color_resolution_context) -{ - ColorResolutionContext fallback_color_resolution_context {}; - if (from.has_color() && to.has_color()) { - if (!color_resolution_context) { - if (auto* node = element.unsafe_layout_node()) - fallback_color_resolution_context = ColorResolutionContext::for_layout_node_with_style(*node); - color_resolution_context = &fallback_color_resolution_context; - } - if (auto interpolated = interpolate_color(from, to, delta, {}, *color_resolution_context)) - return interpolated; - if (from.type() == StyleValue::Type::Color && to.type() == StyleValue::Type::Color) - return ColorStyleValue::create_from_color(Color::Black, ColorSyntax::Modern); - } - - if (from.type() != to.type() || from.is_calculated() || to.is_calculated()) { - // Handle mixed percentage and dimension types, as well as CalculatedStyleValues - // https://www.w3.org/TR/css-values-4/#mixed-percentages - return interpolate_mixed_value(calculation_context, from, to, delta); - } - - switch (from.type()) { - case StyleValue::Type::Angle: { - auto interpolated_value = interpolate_raw(from.as_angle().angle().to_degrees(), to.as_angle().angle().to_degrees(), delta, calculation_context.accepted_ranges_by_type.get(ValueType::Angle)); - return AngleStyleValue::create(Angle::make_degrees(interpolated_value)); - } - case StyleValue::Type::BackgroundSize: { - auto interpolated_x = interpolate_value(element, calculation_context, from.as_background_size().size_x(), to.as_background_size().size_x(), delta, allow_discrete); - auto interpolated_y = interpolate_value(element, calculation_context, from.as_background_size().size_y(), to.as_background_size().size_y(), delta, allow_discrete); - if (!interpolated_x || !interpolated_y) - return {}; - - return BackgroundSizeStyleValue::create(*interpolated_x, *interpolated_y); - } - case StyleValue::Type::BorderImageSlice: { - auto& from_border_image_slice = from.as_border_image_slice(); - auto& to_border_image_slice = to.as_border_image_slice(); - if (from_border_image_slice.fill() != to_border_image_slice.fill()) - return {}; - auto interpolated_top = interpolate_value(element, calculation_context, from_border_image_slice.top(), to_border_image_slice.top(), delta, allow_discrete); - auto interpolated_right = interpolate_value(element, calculation_context, from_border_image_slice.right(), to_border_image_slice.right(), delta, allow_discrete); - auto interpolated_bottom = interpolate_value(element, calculation_context, from_border_image_slice.bottom(), to_border_image_slice.bottom(), delta, allow_discrete); - auto interpolated_left = interpolate_value(element, calculation_context, from_border_image_slice.left(), to_border_image_slice.left(), delta, allow_discrete); - if (!interpolated_top || !interpolated_right || !interpolated_bottom || !interpolated_left) - return {}; - return BorderImageSliceStyleValue::create( - interpolated_top.release_nonnull(), - interpolated_right.release_nonnull(), - interpolated_bottom.release_nonnull(), - interpolated_left.release_nonnull(), - from_border_image_slice.fill()); - } - case StyleValue::Type::BasicShape: { - // https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation - auto& from_shape = from.as_basic_shape().basic_shape(); - auto& to_shape = to.as_basic_shape().basic_shape(); - if (from_shape.index() != to_shape.index()) - return {}; - - CalculationContext basic_shape_calculation_context { - .percentages_resolve_as = ValueType::Length - }; - - auto const interpolate_optional_position = [&](RefPtr from_position, RefPtr to_position) -> Optional> { - if (!from_position && !to_position) - return nullptr; - - auto const& from_position_with_default = from_position ? from_position.release_nonnull() : PositionStyleValue::create_computed_center(); - auto const& to_position_with_default = to_position ? to_position.release_nonnull() : PositionStyleValue::create_computed_center(); - - auto interpolated_position = interpolate_value(element, basic_shape_calculation_context, from_position_with_default, to_position_with_default, delta, allow_discrete); - - // NB: Use OptionalNone to indicate failure to interpolate since nullptr is a valid result for interpolating - // between two null positions. - if (!interpolated_position) - return OptionalNone {}; - - return interpolated_position; - }; - - auto interpolated_shape = from_shape.visit( - [&](Inset const& from_inset) -> Optional { - // If both shapes are of type inset(), interpolate between each value in the shape functions. - auto& to_inset = to_shape.get(); - auto interpolated_top = interpolate_value(element, basic_shape_calculation_context, from_inset.top, to_inset.top, delta, allow_discrete); - auto interpolated_right = interpolate_value(element, basic_shape_calculation_context, from_inset.right, to_inset.right, delta, allow_discrete); - auto interpolated_bottom = interpolate_value(element, basic_shape_calculation_context, from_inset.bottom, to_inset.bottom, delta, allow_discrete); - auto interpolated_left = interpolate_value(element, basic_shape_calculation_context, from_inset.left, to_inset.left, delta, allow_discrete); - - auto interpolated_border_radius = interpolate_value(element, basic_shape_calculation_context, from_inset.border_radius, to_inset.border_radius, delta, allow_discrete); - - if (!interpolated_top || !interpolated_right || !interpolated_bottom || !interpolated_left || !interpolated_border_radius) - return {}; - - return Inset { interpolated_top.release_nonnull(), interpolated_right.release_nonnull(), interpolated_bottom.release_nonnull(), interpolated_left.release_nonnull(), interpolated_border_radius.release_nonnull() }; - }, - [&](Circle const& from_circle) -> Optional { - // If both shapes are the same type, that type is ellipse() or circle(), and the radiuses are specified - // as (rather than keywords), interpolate between each value in the shape functions. - auto const& to_circle = to_shape.get(); - auto interpolated_radius = interpolate_value_impl(element, basic_shape_calculation_context, from_circle.radius, to_circle.radius, delta, AllowDiscrete::No, color_resolution_context); - auto interpolated_position = interpolate_optional_position(from_circle.position, to_circle.position); - if (!interpolated_radius || !interpolated_position.has_value()) - return {}; - - return Circle { interpolated_radius.release_nonnull(), interpolated_position.value() }; - }, - [&](Ellipse const& from_ellipse) -> Optional { - auto const& to_ellipse = to_shape.get(); - auto interpolated_radius = interpolate_value_impl(element, basic_shape_calculation_context, from_ellipse.radius, to_ellipse.radius, delta, AllowDiscrete::No, color_resolution_context); - auto interpolated_position = interpolate_optional_position(from_ellipse.position, to_ellipse.position); - if (!interpolated_radius || !interpolated_position.has_value()) - return {}; - - return Ellipse { interpolated_radius.release_nonnull(), interpolated_position.value() }; - }, - [&](Polygon const& from_polygon) -> Optional { - // If both shapes are of type polygon(), both polygons have the same number of vertices, and use the - // same <'fill-rule'>, interpolate between each value in the shape functions. - auto const& to_polygon = to_shape.get(); - if (from_polygon.fill_rule != to_polygon.fill_rule) - return {}; - if (from_polygon.points.size() != to_polygon.points.size()) - return {}; - Vector interpolated_points; - interpolated_points.ensure_capacity(from_polygon.points.size()); - for (size_t i = 0; i < from_polygon.points.size(); i++) { - auto const& from_point = from_polygon.points[i]; - auto const& to_point = to_polygon.points[i]; - auto interpolated_point_x = interpolate_value(element, basic_shape_calculation_context, from_point.x, to_point.x, delta, allow_discrete); - auto interpolated_point_y = interpolate_value(element, basic_shape_calculation_context, from_point.y, to_point.y, delta, allow_discrete); - if (!interpolated_point_x || !interpolated_point_y) - return {}; - interpolated_points.unchecked_append(Polygon::Point { *interpolated_point_x, *interpolated_point_y }); - } - - return Polygon { from_polygon.fill_rule, move(interpolated_points) }; - }, - [](auto&) -> Optional { - return {}; - }); - - if (!interpolated_shape.has_value()) - return {}; - - return BasicShapeStyleValue::create(*interpolated_shape); - } - case StyleValue::Type::BorderRadius: { - auto const& from_horizontal_radius = from.as_border_radius().horizontal_radius(); - auto const& to_horizontal_radius = to.as_border_radius().horizontal_radius(); - auto const& from_vertical_radius = from.as_border_radius().vertical_radius(); - auto const& to_vertical_radius = to.as_border_radius().vertical_radius(); - auto interpolated_horizontal_radius = interpolate_value_impl(element, calculation_context, from_horizontal_radius, to_horizontal_radius, delta, allow_discrete, color_resolution_context); - auto interpolated_vertical_radius = interpolate_value_impl(element, calculation_context, from_vertical_radius, to_vertical_radius, delta, allow_discrete, color_resolution_context); - if (!interpolated_horizontal_radius || !interpolated_vertical_radius) - return {}; - return BorderRadiusStyleValue::create(interpolated_horizontal_radius.release_nonnull(), interpolated_vertical_radius.release_nonnull()); - } - case StyleValue::Type::BorderRadiusRect: { - CalculationContext border_radius_rect_computation_context = { - .percentages_resolve_as = ValueType::Length, - .accepted_ranges_by_type = { { ValueType::Length, { 0, AK::NumericLimits::max() } }, { ValueType::Percentage, { 0, AK::NumericLimits::max() } } }, - }; - - auto const& from_top_left = from.as_border_radius_rect().top_left(); - auto const& to_top_left = to.as_border_radius_rect().top_left(); - - auto const& from_top_right = from.as_border_radius_rect().top_right(); - auto const& to_top_right = to.as_border_radius_rect().top_right(); - - auto const& from_bottom_right = from.as_border_radius_rect().bottom_right(); - auto const& to_bottom_right = to.as_border_radius_rect().bottom_right(); - - auto const& from_bottom_left = from.as_border_radius_rect().bottom_left(); - auto const& to_bottom_left = to.as_border_radius_rect().bottom_left(); - - auto interpolated_top_left = interpolate_value_impl(element, border_radius_rect_computation_context, from_top_left, to_top_left, delta, allow_discrete, color_resolution_context); - auto interpolated_top_right = interpolate_value_impl(element, border_radius_rect_computation_context, from_top_right, to_top_right, delta, allow_discrete, color_resolution_context); - auto interpolated_bottom_right = interpolate_value_impl(element, border_radius_rect_computation_context, from_bottom_right, to_bottom_right, delta, allow_discrete, color_resolution_context); - auto interpolated_bottom_left = interpolate_value_impl(element, border_radius_rect_computation_context, from_bottom_left, to_bottom_left, delta, allow_discrete, color_resolution_context); - - if (!interpolated_top_left || !interpolated_top_right || !interpolated_bottom_right || !interpolated_bottom_left) - return {}; - - return BorderRadiusRectStyleValue::create(interpolated_top_left.release_nonnull(), interpolated_top_right.release_nonnull(), interpolated_bottom_right.release_nonnull(), interpolated_bottom_left.release_nonnull()); - } - case StyleValue::Type::Color: - VERIFY_NOT_REACHED(); - case StyleValue::Type::Edge: { - auto const& from_offset = from.as_edge().offset(); - auto const& to_offset = to.as_edge().offset(); - - if (auto interpolated_value = interpolate_value_impl(element, calculation_context, from_offset, to_offset, delta, allow_discrete, color_resolution_context)) - return EdgeStyleValue::create({}, interpolated_value); - - return {}; - } - case StyleValue::Type::FontStyle: { - auto const& from_font_style = from.as_font_style(); - auto const& to_font_style = to.as_font_style(); - auto interpolated_font_style = interpolate_value(element, calculation_context, KeywordStyleValue::create(to_keyword(from_font_style.font_style())), KeywordStyleValue::create(to_keyword(to_font_style.font_style())), delta, allow_discrete); - if (!interpolated_font_style) - return {}; - if (from_font_style.angle() && to_font_style.angle()) { - auto interpolated_angle = interpolate_value(element, { .accepted_ranges_by_type = { { ValueType::Angle, { -90, 90 } } } }, *from_font_style.angle(), *to_font_style.angle(), delta, allow_discrete); - if (!interpolated_angle) - return {}; - return FontStyleStyleValue::create(*keyword_to_font_style_keyword(interpolated_font_style->to_keyword()), interpolated_angle); - } - - return FontStyleStyleValue::create(*keyword_to_font_style_keyword(interpolated_font_style->to_keyword())); - } - case StyleValue::Type::Flex: { - auto interpolated_value = interpolate_raw(from.as_flex().flex().to_fr(), to.as_flex().flex().to_fr(), delta, calculation_context.accepted_ranges_by_type.get(ValueType::Flex)); - return FlexStyleValue::create(Flex::make_fr(interpolated_value)); - } - case StyleValue::Type::Function: { - auto const& from_function = from.as_function(); - auto const& to_function = to.as_function(); - - if (from_function.name() != to_function.name()) - return {}; - - auto interpolated_value = interpolate_value(element, calculation_context, from_function.value(), to_function.value(), delta, allow_discrete); - if (!interpolated_value) - return {}; - - return FunctionStyleValue::create(from_function.name(), interpolated_value.release_nonnull()); - } - case StyleValue::Type::Integer: { - // https://drafts.csswg.org/css-values/#combine-integers - // Interpolation of is defined as Vresult = round((1 - p) × VA + p × VB); - // that is, interpolation happens in the real number space as for s, and the result is converted to an by rounding to the nearest integer. - auto interpolated_value = interpolate_raw(from.as_integer().integer(), to.as_integer().integer(), delta, calculation_context.accepted_ranges_by_type.get(ValueType::Integer)); - return IntegerStyleValue::create(interpolated_value); - } - case StyleValue::Type::Length: { - auto const& from_length = from.as_length().length(); - auto const& to_length = to.as_length().length(); - auto interpolated_value = interpolate_raw(from_length.raw_value(), to_length.raw_value(), delta, calculation_context.accepted_ranges_by_type.get(ValueType::Length)); - return LengthStyleValue::create(Length(interpolated_value, from_length.unit())); - } - case StyleValue::Type::Number: { - auto interpolated_value = interpolate_raw(from.as_number().number(), to.as_number().number(), delta, calculation_context.accepted_ranges_by_type.get(ValueType::Number)); - return NumberStyleValue::create(interpolated_value); - } - case StyleValue::Type::OpacityValue: { - auto interpolated_value = interpolate_raw(from.as_opacity_value().resolved(), to.as_opacity_value().resolved(), delta, NumericRange { .min = 0, .max = 1 }); - return OpacityValueStyleValue::create(NumberStyleValue::create(interpolated_value)); - } - case StyleValue::Type::OpenTypeTagged: { - auto& from_open_type_tagged = from.as_open_type_tagged(); - auto& to_open_type_tagged = to.as_open_type_tagged(); - if (from_open_type_tagged.tag() != to_open_type_tagged.tag()) - return {}; - auto interpolated_value = interpolate_value(element, calculation_context, from_open_type_tagged.value(), to_open_type_tagged.value(), delta, allow_discrete); - if (!interpolated_value) - return {}; - return OpenTypeTaggedStyleValue::create(OpenTypeTaggedStyleValue::Mode::FontVariationSettings, from_open_type_tagged.tag(), interpolated_value.release_nonnull()); - } - case StyleValue::Type::Percentage: { - auto interpolated_value = interpolate_raw(from.as_percentage().percentage().value(), to.as_percentage().percentage().value(), delta, calculation_context.accepted_ranges_by_type.get(ValueType::Percentage)); - return PercentageStyleValue::create(Percentage(interpolated_value)); - } - case StyleValue::Type::Position: { - // https://www.w3.org/TR/css-values-4/#combine-positions - // FIXME: Interpolation of is defined as the independent interpolation of each component (x, y) normalized as an offset from the top left corner as a . - auto const& from_position = from.as_position(); - auto const& to_position = to.as_position(); - auto interpolated_edge_x = interpolate_value(element, calculation_context, from_position.edge_x(), to_position.edge_x(), delta, allow_discrete); - auto interpolated_edge_y = interpolate_value(element, calculation_context, from_position.edge_y(), to_position.edge_y(), delta, allow_discrete); - if (!interpolated_edge_x || !interpolated_edge_y) - return {}; - return PositionStyleValue::create(interpolated_edge_x->as_edge(), interpolated_edge_y->as_edge()); - } - case StyleValue::Type::RadialSize: { - auto const& from_components = from.as_radial_size().components(); - auto const& to_components = to.as_radial_size().components(); - - auto const is_radial_extent = [](auto const& component) { return component.template has(); }; - - // https://drafts.csswg.org/css-images-4/#interpolating-gradients - // https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation - // FIXME: Radial extents should disallow interpolation for basic-shape values but should be converted into their - // equivalent length-percentage values for radial gradients - if (any_of(from_components, is_radial_extent) || any_of(to_components, is_radial_extent)) - return {}; - - CalculationContext radial_size_calculation_context { - .percentages_resolve_as = ValueType::Length, - .accepted_ranges_by_type = { - { ValueType::Length, { 0, AK::NumericLimits::max() } }, - } - }; - - if (from_components.size() == 1 && to_components.size() == 1) { - auto const& from_component = from_components[0].get>(); - auto const& to_component = to_components[0].get>(); - - auto interpolated_value = interpolate_value(element, radial_size_calculation_context, from_component, to_component, delta, allow_discrete); - - if (!interpolated_value) - return {}; - - return RadialSizeStyleValue::create({ interpolated_value.release_nonnull() }); - } - - auto const& from_horizontal_component = from_components[0].get>(); - auto const& from_vertical_component = from_components.size() > 1 ? from_components[1].get>() : from_horizontal_component; - - auto const& to_horizontal_component = to_components[0].get>(); - auto const& to_vertical_component = to_components.size() > 1 ? to_components[1].get>() : to_horizontal_component; - - auto interpolated_horizontal = interpolate_value(element, radial_size_calculation_context, from_horizontal_component, to_horizontal_component, delta, allow_discrete); - auto interpolated_vertical = interpolate_value(element, radial_size_calculation_context, from_vertical_component, to_vertical_component, delta, allow_discrete); - - if (!interpolated_horizontal || !interpolated_vertical) - return {}; - - return RadialSizeStyleValue::create({ interpolated_horizontal.release_nonnull(), interpolated_vertical.release_nonnull() }); - } - case StyleValue::Type::Ratio: { - auto from_ratio = from.as_ratio().resolved(); - auto to_ratio = to.as_ratio().resolved(); - - // https://drafts.csswg.org/css-values/#combine-ratio - // If either is degenerate, the values cannot be interpolated. - if (from_ratio.is_degenerate() || to_ratio.is_degenerate()) - return {}; - - // The interpolation of a is defined by converting each to a number by dividing the first value - // by the second (so a ratio of 3 / 2 would become 1.5), taking the logarithm of that result (so the 1.5 would - // become approximately 0.176), then interpolating those values. The result during the interpolation is - // converted back to a by inverting the logarithm, then interpreting the result as a with the - // result as the first value and 1 as the second value. - auto from_number = log(from_ratio.value()); - auto to_number = log(to_ratio.value()); - auto interpolated_value = interpolate_raw(from_number, to_number, delta, calculation_context.accepted_ranges_by_type.get(ValueType::Ratio)); - return RatioStyleValue::create(NumberStyleValue::create(pow(M_E, interpolated_value)), NumberStyleValue::create(1)); - } - case StyleValue::Type::Rect: { - auto const& from_rect = from.as_rect(); - auto const& to_rect = to.as_rect(); - - auto interpolated_top = interpolate_value_impl(element, calculation_context, from_rect.top(), to_rect.top(), delta, allow_discrete, color_resolution_context); - auto interpolated_right = interpolate_value_impl(element, calculation_context, from_rect.right(), to_rect.right(), delta, allow_discrete, color_resolution_context); - auto interpolated_bottom = interpolate_value_impl(element, calculation_context, from_rect.bottom(), to_rect.bottom(), delta, allow_discrete, color_resolution_context); - auto interpolated_left = interpolate_value_impl(element, calculation_context, from_rect.left(), to_rect.left(), delta, allow_discrete, color_resolution_context); - - if (!interpolated_top || !interpolated_right || !interpolated_bottom || !interpolated_left) - return {}; - - return RectStyleValue::create(interpolated_top.release_nonnull(), interpolated_right.release_nonnull(), interpolated_bottom.release_nonnull(), interpolated_left.release_nonnull()); - } - case StyleValue::Type::TextIndent: { - auto& from_text_indent = from.as_text_indent(); - auto& to_text_indent = to.as_text_indent(); - - if (from_text_indent.each_line() != to_text_indent.each_line() - || from_text_indent.hanging() != to_text_indent.hanging()) - return {}; - - auto interpolated_length_percentage = interpolate_value(element, calculation_context, from_text_indent.length_percentage(), to_text_indent.length_percentage(), delta, allow_discrete); - if (!interpolated_length_percentage) - return {}; - - return TextIndentStyleValue::create(interpolated_length_percentage.release_nonnull(), - from_text_indent.hanging() ? TextIndentStyleValue::Hanging::Yes : TextIndentStyleValue::Hanging::No, - from_text_indent.each_line() ? TextIndentStyleValue::EachLine::Yes : TextIndentStyleValue::EachLine::No); - } - case StyleValue::Type::Superellipse: { - // https://drafts.csswg.org/css-borders-4/#corner-shape-interpolation - - // https://drafts.csswg.org/css-borders-4/#normalized-superellipse-half-corner - auto normalized_super_ellipse_half_corner = [](double s) -> double { - // To compute the normalized superellipse half corner given a superellipse parameter s, return the first matching statement, switching on s: - - // -∞ Return 0. - if (s == -AK::Infinity) - return 0; - - // ∞ Return 1. - if (s == AK::Infinity) - return 1; - - // Otherwise - // 1. Let k be 0.5^abs(s). - auto k = pow(0.5, abs(s)); - - // 2. Let convexHalfCorner be 0.5^k. - auto convex_half_corner = pow(0.5, k); - - // 3. If s is less than 0, return 1 - convexHalfCorner. - if (s < 0) - return 1 - convex_half_corner; - - // 4. Return convexHalfCorner. - return convex_half_corner; - }; - - auto interpolation_value_to_super_ellipse_parameter = [](double interpolation_value) -> double { - // To convert a interpolationValue back to a superellipse parameter, switch on interpolationValue: - - // 0 Return -∞. - if (interpolation_value == 0) - return -AK::Infinity; - - // 0.5 Return 0. - if (interpolation_value == 0.5) - return 0; - - // 1 Return ∞. - if (interpolation_value == 1) - return AK::Infinity; - - // Otherwise - // 1. Let convexHalfCorner be interpolationValue. - auto convex_half_corner = interpolation_value; - - // 2. If interpolationValue is less than 0.5, set convexHalfCorner to 1 - interpolationValue. - if (interpolation_value < 0.5) - convex_half_corner = 1 - interpolation_value; - - // 3. Let k be ln(0.5) / ln(convexHalfCorner). - auto k = log(0.5) / log(convex_half_corner); - - // 4. Let s be log2(k). - auto s = log2(k); - - // AD-HOC: The logs above can introduce slight inaccuracies, this can interfere with the behaviour of - // serializing superellipse style values as their equivalent keywords as that relies on exact - // equality. To mitigate this we simply round to a whole number if we are sufficiently near - if (abs(round(s) - s) < AK::NumericLimits::epsilon()) - s = round(s); - - // 5. If interpolationValue is less than 0.5, return -s. - if (interpolation_value < 0.5) - return -s; - - // 6. Return s. - return s; - }; - - auto from_normalized_value = normalized_super_ellipse_half_corner(from.as_superellipse().parameter()); - auto to_normalized_value = normalized_super_ellipse_half_corner(to.as_superellipse().parameter()); - - auto interpolated_value = interpolate_raw(from_normalized_value, to_normalized_value, delta, NumericRange { .min = 0, .max = 1 }); - - return SuperellipseStyleValue::create(NumberStyleValue::create(interpolation_value_to_super_ellipse_parameter(interpolated_value))); - } - case StyleValue::Type::Transformation: - VERIFY_NOT_REACHED(); - case StyleValue::Type::ValueList: { - auto const& from_list = from.as_value_list(); - auto const& to_list = to.as_value_list(); - if (from_list.size() != to_list.size()) - return {}; - - // FIXME: If the number of components or the types of corresponding components do not match, - // or if any component value uses discrete animation and the two corresponding values do not match, - // then the property values combine as discrete. - StyleValueVector interpolated_values; - interpolated_values.ensure_capacity(from_list.size()); - for (size_t i = 0; i < from_list.size(); ++i) { - auto interpolated = interpolate_value(element, calculation_context, from_list.values()[i], to_list.values()[i], delta, AllowDiscrete::No, color_resolution_context); - if (!interpolated) - return {}; - - interpolated_values.append(*interpolated); - } - - return StyleValueList::create(move(interpolated_values), from_list.separator()); - } - default: - return {}; - } -} - -RefPtr interpolate_repeatable_list(DOM::Element& element, CalculationContext const& calculation_context, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete allow_discrete, ColorResolutionContext const* color_resolution_context) -{ - // https://www.w3.org/TR/web-animations/#repeatable-list - // Same as by computed value except that if the two lists have differing numbers of items, they are first repeated to the least common multiple number of items. - // Each item is then combined by computed value. - // If a pair of values cannot be combined or if any component value uses discrete animation, then the property values combine as discrete. - - auto make_repeatable_list = [&](auto const& from_list, auto const& to_list, Function)> append_callback) -> bool { - // If the number of components or the types of corresponding components do not match, - // or if any component value uses discrete animation and the two corresponding values do not match, - // then the property values combine as discrete - auto list_size = AK::lcm(from_list.size(), to_list.size()); - for (size_t i = 0; i < list_size; ++i) { - auto value = interpolate_value(element, calculation_context, from_list.value_at(i, true), to_list.value_at(i, true), delta, AllowDiscrete::No, color_resolution_context); - if (!value) - return false; - append_callback(*value); - } - - return true; - }; - - auto make_single_value_list = [&](auto const& value, size_t size, auto separator) { - StyleValueVector values; - values.ensure_capacity(size); - for (size_t i = 0; i < size; ++i) - values.append(value); - return StyleValueList::create(move(values), separator); - }; - - NonnullRefPtr from_list = from; - NonnullRefPtr to_list = to; - if (!from.is_value_list() && to.is_value_list()) - from_list = make_single_value_list(from, to.as_value_list().size(), to.as_value_list().separator()); - else if (!to.is_value_list() && from.is_value_list()) - to_list = make_single_value_list(to, from.as_value_list().size(), from.as_value_list().separator()); - else if (!from.is_value_list() && !to.is_value_list()) - return interpolate_value(element, calculation_context, from, to, delta, allow_discrete, color_resolution_context); - - StyleValueVector interpolated_values; - if (!make_repeatable_list(from_list->as_value_list(), to_list->as_value_list(), [&](auto const& value) { interpolated_values.append(value); })) - return interpolate_discrete(from, to, delta, allow_discrete); - return StyleValueList::create(move(interpolated_values), from_list->as_value_list().separator()); -} - -// https://drafts.csswg.org/filter-effects/#accumulation -static StyleValueVector accumulate_filter_function(StyleValueList const& underlying_list, StyleValueList const& animated_list, ColorResolutionContext const& color_resolution_context) -{ - // Accumulation of s follows the same matching and extending rules as interpolation, falling - // back to replace behavior if the lists do not match. However instead of interpolating the matching - // pairs, their arguments are arithmetically added together - except in the case of - // s whose initial value for interpolation is 1, which combine using one-based addition: - // Vresult = Va + Vb - 1 - - if (contains_url(underlying_list) || contains_url(animated_list)) - return {}; - - auto accumulate_filter = [&](FilterStyleValue const& underlying, FilterStyleValue const& animated) -> RefPtr { - if (underlying.kind() != animated.kind()) - return {}; - - switch (underlying.kind()) { - case FilterStyleValue::Kind::Blur: { - auto const& underlying_blur = static_cast(underlying); - auto const& animated_blur = static_cast(animated); - return BlurFilterStyleValue::create(LengthStyleValue::create(Length::make_px(underlying_blur.resolved_radius() + animated_blur.resolved_radius()))); - } - case FilterStyleValue::Kind::HueRotate: { - auto const& underlying_rotate = static_cast(underlying); - auto const& animated_rotate = static_cast(animated); - return HueRotateFilterStyleValue::create(AngleStyleValue::create(Angle::make_degrees(underlying_rotate.angle_degrees() + animated_rotate.angle_degrees()))); - } - case FilterStyleValue::Kind::Color: { - auto const& underlying_color = static_cast(underlying); - auto const& animated_color = static_cast(animated); - if (underlying_color.operation() != animated_color.operation()) - return {}; - - auto underlying_amount = underlying_color.resolved_amount(); - auto animated_amount = animated_color.resolved_amount(); - - double accumulated; - switch (underlying_color.operation()) { - case Gfx::ColorFilterType::Brightness: - case Gfx::ColorFilterType::Contrast: - case Gfx::ColorFilterType::Opacity: - case Gfx::ColorFilterType::Saturate: - accumulated = underlying_amount + animated_amount - 1.0; - break; - case Gfx::ColorFilterType::Grayscale: - case Gfx::ColorFilterType::Invert: - case Gfx::ColorFilterType::Sepia: - accumulated = underlying_amount + animated_amount; - break; - default: - VERIFY_NOT_REACHED(); - } - - return ColorFilterStyleValue::create(underlying_color.operation(), NumberStyleValue::create(accumulated)); - } - case FilterStyleValue::Kind::DropShadow: { - auto const& underlying_shadow = static_cast(underlying); - auto const& animated_shadow = static_cast(animated); - - auto add_lengths = [](NonnullRefPtr const& a, NonnullRefPtr const& b) -> NonnullRefPtr { - auto a_value = Length::from_style_value(a, {}).absolute_length_to_px_without_rounding(); - auto b_value = Length::from_style_value(b, {}).absolute_length_to_px_without_rounding(); - - return LengthStyleValue::create(Length::make_px(a_value + b_value)); - }; - - auto offset_x = add_lengths(underlying_shadow.offset_x(), animated_shadow.offset_x()); - auto offset_y = add_lengths(underlying_shadow.offset_y(), animated_shadow.offset_y()); - RefPtr accumulated_radius; - if (underlying_shadow.radius() || animated_shadow.radius()) { - auto underlying_radius = underlying_shadow.radius() ? Length::from_style_value(*underlying_shadow.radius(), {}).absolute_length_to_px_without_rounding() : 0; - auto animated_radius = animated_shadow.radius() ? Length::from_style_value(*animated_shadow.radius(), {}).absolute_length_to_px_without_rounding() : 0; - accumulated_radius = LengthStyleValue::create(Length::make_px(underlying_radius + animated_radius)); - } - - auto element_color = color_resolution_context.current_color.value_or(Color::Black); - auto resolve_color = [&](RefPtr const& color) { - return color ? color->to_color(color_resolution_context).value_or(element_color) : element_color; - }; - auto underlying_color = resolve_color(underlying_shadow.color()); - auto animated_color = resolve_color(animated_shadow.color()); - auto accumulated = Color( - min(255, underlying_color.red() + animated_color.red()), - min(255, underlying_color.green() + animated_color.green()), - min(255, underlying_color.blue() + animated_color.blue()), - min(255, underlying_color.alpha() + animated_color.alpha())); - auto accumulated_color = ColorStyleValue::create_from_color(accumulated, ColorSyntax::Legacy); - - return DropShadowFilterStyleValue::create( - offset_x, - offset_y, - accumulated_radius, - accumulated_color); - } - } - VERIFY_NOT_REACHED(); - }; - - // Extend shorter list with initial values - size_t max_size = max(underlying_list.size(), animated_list.size()); - StyleValueVector extended_underlying; - StyleValueVector extended_animated; - - for (size_t i = 0; i < max_size; ++i) { - if (i < underlying_list.size()) - extended_underlying.append(underlying_list.values()[i]); - else - extended_underlying.append(FilterStyleValue::initial_value_for(animated_list.values()[i]->as_filter(), false)); - - if (i < animated_list.size()) - extended_animated.append(animated_list.values()[i]); - else - extended_animated.append(FilterStyleValue::initial_value_for(underlying_list.values()[i]->as_filter(), false)); - } - - StyleValueVector result; - result.ensure_capacity(max_size); - for (size_t i = 0; i < max_size; ++i) { - auto accumulated = accumulate_filter(extended_underlying[i]->as_filter(), extended_animated[i]->as_filter()); - if (!accumulated) - return {}; - result.unchecked_append(accumulated.release_nonnull()); - } - return result; -} - -RefPtr interpolate_value(DOM::Element& element, CalculationContext const& calculation_context, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete allow_discrete, ColorResolutionContext const* color_resolution_context) -{ - if (auto result = interpolate_value_impl(element, calculation_context, from, to, delta, allow_discrete, color_resolution_context)) - return result; - return interpolate_discrete(from, to, delta, allow_discrete); -} - -template -static T composite_raw_values(T underlying_raw_value, T animated_raw_value) -{ - return underlying_raw_value + animated_raw_value; -} - -static Optional composite_grid_track_size_list(PropertyID property_id, CalculationContext const& calculation_context, GridTrackSizeList const& underlying, GridTrackSizeList const& animated, Bindings::CompositeOperation composite_operation) -{ - // https://drafts.csswg.org/css-grid-2/#track-sizing - // Animation type: if the list lengths match, by computed value type per item in the computed track list; - // discrete otherwise. - // - // https://drafts.csswg.org/css-grid-2/#computed-track-list-subgrid - // The computed track list of a subgrid axis is the subgrid keyword followed by a list of line names. - if (underlying.is_subgrid() || animated.is_subgrid()) - return {}; - - auto composite_grid_size = [&](GridSize const& underlying_grid_size, GridSize const& animated_grid_size) -> Optional { - if (auto composited_value = composite_value(property_id, underlying_grid_size.style_value(), animated_grid_size.style_value(), composite_operation)) - return GridSize { *composited_value }; - - return {}; - }; - - auto expanded_underlying = expand_grid_tracks_and_lines(underlying); - auto expanded_animated = expand_grid_tracks_and_lines(animated); - - if (expanded_underlying.tracks.size() != expanded_animated.tracks.size()) - return {}; - - GridTrackSizeList result; - for (size_t i = 0; i < expanded_underlying.tracks.size(); ++i) { - auto& underlying_track = expanded_underlying.tracks[i]; - auto& animated_track = expanded_animated.tracks[i]; - auto composited_line_names = move(expanded_animated.line_names[i]); - - if (underlying_track.is_repeat() || animated_track.is_repeat()) { - if (!underlying_track.is_repeat() || !animated_track.is_repeat()) - return {}; - - auto underlying_repeat = underlying_track.repeat(); - auto animated_repeat = animated_track.repeat(); - if (!underlying_repeat.is_fixed() || !animated_repeat.is_fixed()) - return {}; - if (underlying_repeat.repeat_count() != animated_repeat.repeat_count() || underlying_repeat.grid_track_size_list().track_list().size() != animated_repeat.grid_track_size_list().track_list().size()) - return {}; - - auto composited_repeat_grid_tracks = composite_grid_track_size_list(property_id, calculation_context, underlying_repeat.grid_track_size_list(), animated_repeat.grid_track_size_list(), composite_operation); - if (!composited_repeat_grid_tracks.has_value()) - return {}; - - ExplicitGridTrack composited_grid_track { GridRepeat { underlying_repeat.type(), move(*composited_repeat_grid_tracks), IntegerStyleValue::create(underlying_repeat.repeat_count()) } }; - append_grid_track_with_line_names(result, move(composited_grid_track), move(composited_line_names)); - continue; - } - - if (underlying_track.is_minmax() && animated_track.is_minmax()) { - auto underlying_minmax = underlying_track.minmax(); - auto animated_minmax = animated_track.minmax(); - auto composited_min = composite_grid_size(underlying_minmax.min_grid_size(), animated_minmax.min_grid_size()); - auto composited_max = composite_grid_size(underlying_minmax.max_grid_size(), animated_minmax.max_grid_size()); - ExplicitGridTrack composited_grid_track { GridMinMax { - composited_min.value_or(animated_minmax.min_grid_size()), - composited_max.value_or(animated_minmax.max_grid_size()) } }; - append_grid_track_with_line_names(result, move(composited_grid_track), move(composited_line_names)); - continue; - } - if (underlying_track.is_default() && animated_track.is_default()) { - auto const& underlying_grid_size = underlying_track.grid_size(); - auto const& animated_grid_size = animated_track.grid_size(); - auto composited_grid_size_result = composite_grid_size(underlying_grid_size, animated_grid_size); - if (composited_grid_size_result.has_value()) { - ExplicitGridTrack composited_grid_track { move(*composited_grid_size_result) }; - append_grid_track_with_line_names(result, move(composited_grid_track), move(composited_line_names)); - continue; - } - } - append_grid_track_with_line_names(result, animated_track, move(composited_line_names)); - } - return result; -} - -static RefPtr composite_mixed_value(StyleValue const& underlying_value, StyleValue const& animated_value, CalculationContext const& calculation_context) -{ - // https://drafts.csswg.org/css-values-4/#combine-mixed - // Addition of is defined the same as interpolation except by adding each component rather than interpolating it. - auto underlying_value_type = get_value_type_of_numeric_style_value(underlying_value, calculation_context); - auto animated_value_type = get_value_type_of_numeric_style_value(animated_value, calculation_context); - - if (underlying_value_type.has_value() && underlying_value_type == animated_value_type) { - // The computed value of a percentage-dimension mix is defined as - // FIXME: a computed dimension if the percentage component is zero or is defined specifically to compute to a dimension value - // a computed percentage if the dimension component is zero - // a computed calc() expression otherwise - if (auto const* from_dimension_value = as_if(underlying_value); from_dimension_value && animated_value.type() == StyleValue::Type::Percentage) { - auto dimension_component = from_dimension_value->raw_value(); - auto percentage_component = animated_value.as_percentage().raw_value(); - if (dimension_component == 0.f) - return PercentageStyleValue::create(Percentage { percentage_component }); - } else if (auto const* to_dimension_value = as_if(animated_value); to_dimension_value && underlying_value.type() == StyleValue::Type::Percentage) { - auto dimension_component = to_dimension_value->raw_value(); - auto percentage_component = underlying_value.as_percentage().raw_value(); - if (dimension_component == 0) - return PercentageStyleValue::create(Percentage { percentage_component }); - } - - Vector contributions; - contributions.append(CalcNodeRef::from_style_value(underlying_value)); - contributions.append(CalcNodeRef::from_style_value(animated_value)); - auto composited_sum = CalcNodeRef::sum(move(contributions)); - - auto numeric_type = composited_sum.determine_type(calculation_context); - return CalculatedStyleValue::create( - simplify_a_calculation_tree(composited_sum, calculation_context, {}), - numeric_type.value(), - calculation_context); - } - - return {}; -} - -RefPtr composite_value(PropertyID property_id, StyleValue const& underlying_value, StyleValue const& animated_value, Bindings::CompositeOperation composite_operation, ColorResolutionContext const& color_resolution_context) -{ - auto calculation_context = CalculationContext::for_property(PropertyNameAndID::from_id(property_id)); - - auto composite_dimension_value = [](StyleValue const& underlying_value, StyleValue const& animated_value) -> Optional { - auto const& underlying_dimension = as(underlying_value); - auto const& animated_dimension = as(animated_value); - return composite_raw_values(underlying_dimension.raw_value(), animated_dimension.raw_value()); - }; - - if (composite_operation == Bindings::CompositeOperation::Replace) - return {}; - - if (underlying_value.type() != animated_value.type() || underlying_value.is_calculated() || animated_value.is_calculated()) - return composite_mixed_value(underlying_value, animated_value, calculation_context); - - switch (underlying_value.type()) { - case StyleValue::Type::Angle: { - auto result = composite_dimension_value(underlying_value, animated_value); - if (!result.has_value()) - return {}; - VERIFY(underlying_value.as_angle().angle().unit() == animated_value.as_angle().angle().unit()); - return AngleStyleValue::create({ *result, underlying_value.as_angle().angle().unit() }); - } - case StyleValue::Type::BasicShape: { - auto const& underlying_basic_shape = underlying_value.as_basic_shape(); - auto const& animated_basic_shape = animated_value.as_basic_shape(); - - if (underlying_basic_shape.basic_shape().index() != animated_basic_shape.basic_shape().index()) - return {}; - - return underlying_basic_shape.basic_shape().visit( - [&](Inset const& underlying_inset) -> RefPtr { - auto const& animated_inset = animated_basic_shape.basic_shape().get(); - auto composited_top = composite_value(property_id, underlying_inset.top, animated_inset.top, composite_operation); - auto composited_right = composite_value(property_id, underlying_inset.right, animated_inset.right, composite_operation); - auto composited_bottom = composite_value(property_id, underlying_inset.bottom, animated_inset.bottom, composite_operation); - auto composited_left = composite_value(property_id, underlying_inset.left, animated_inset.left, composite_operation); - auto composited_border_radius = composite_value(property_id, underlying_inset.border_radius, animated_inset.border_radius, composite_operation); - if (!composited_top || !composited_right || !composited_bottom || !composited_left || !composited_border_radius) - return {}; - - return BasicShapeStyleValue::create(Inset { composited_top.release_nonnull(), composited_right.release_nonnull(), composited_bottom.release_nonnull(), composited_left.release_nonnull(), composited_border_radius.release_nonnull() }); - }, - [&](Circle const& underlying_circle) -> RefPtr { - auto const& animated_circle = animated_basic_shape.basic_shape().get(); - auto composited_radius = composite_value(property_id, underlying_circle.radius, animated_circle.radius, composite_operation); - if (!composited_radius) - return {}; - - RefPtr composited_position; - if (underlying_circle.position || animated_circle.position) { - auto const& underlying_position_with_default = underlying_circle.position ? ValueComparingNonnullRefPtr { *underlying_circle.position } : PositionStyleValue::create_computed_center(); - auto const& animated_position_with_default = animated_circle.position ? ValueComparingNonnullRefPtr { *animated_circle.position } : PositionStyleValue::create_computed_center(); - - composited_position = composite_value(property_id, underlying_position_with_default, animated_position_with_default, composite_operation); - - if (!composited_position) - return {}; - } - - return BasicShapeStyleValue::create(Circle { composited_radius.release_nonnull(), composited_position }); - }, - [&](Ellipse const& underlying_ellipse) -> RefPtr { - auto const& animated_ellipse = animated_basic_shape.basic_shape().get(); - auto composited_radius = composite_value(property_id, underlying_ellipse.radius, animated_ellipse.radius, composite_operation); - if (!composited_radius) - return {}; - - RefPtr composited_position; - if (underlying_ellipse.position || animated_ellipse.position) { - auto const& underlying_position_with_default = underlying_ellipse.position ? ValueComparingNonnullRefPtr { *underlying_ellipse.position } : PositionStyleValue::create_computed_center(); - auto const& animated_position_with_default = animated_ellipse.position ? ValueComparingNonnullRefPtr { *animated_ellipse.position } : PositionStyleValue::create_computed_center(); - - composited_position = composite_value(property_id, underlying_position_with_default, animated_position_with_default, composite_operation); - - if (!composited_position) - return {}; - } - - return BasicShapeStyleValue::create(Ellipse { composited_radius.release_nonnull(), composited_position }); - }, - [&](Polygon const& underlying_polygon) -> RefPtr { - auto const& animated_polygon = animated_basic_shape.basic_shape().get(); - if (underlying_polygon.fill_rule != animated_polygon.fill_rule) - return {}; - - if (underlying_polygon.points.size() != animated_polygon.points.size()) - return {}; - - Vector composited_points; - composited_points.ensure_capacity(underlying_polygon.points.size()); - for (size_t i = 0; i < underlying_polygon.points.size(); i++) { - auto const& underlying_point = underlying_polygon.points[i]; - auto const& animated_point = animated_polygon.points[i]; - auto composited_point_x = composite_value(property_id, underlying_point.x, animated_point.x, composite_operation); - auto composited_point_y = composite_value(property_id, underlying_point.y, animated_point.y, composite_operation); - if (!composited_point_x || !composited_point_y) - return {}; - composited_points.unchecked_append(Polygon::Point { *composited_point_x, *composited_point_y }); - } - - return BasicShapeStyleValue::create(Polygon { underlying_polygon.fill_rule, move(composited_points) }); - }, - [&](Xywh const&) -> RefPtr { - // xywh() should have been absolutized into inset() before now - VERIFY_NOT_REACHED(); - }, - [&](Rect const&) -> RefPtr { - // rect() should have been absolutized into inset() before now - VERIFY_NOT_REACHED(); - }, - [&](Path const&) -> RefPtr { - // FIXME: Implement composition for path() - return {}; - }); - } - case StyleValue::Type::BorderImageSlice: { - auto& underlying_border_image_slice_value = underlying_value.as_border_image_slice(); - auto& animated_border_image_slice_value = animated_value.as_border_image_slice(); - if (underlying_border_image_slice_value.fill() != animated_border_image_slice_value.fill()) - return {}; - auto composited_top = composite_value(property_id, underlying_border_image_slice_value.top(), animated_border_image_slice_value.top(), composite_operation); - auto composited_right = composite_value(property_id, underlying_border_image_slice_value.right(), animated_border_image_slice_value.right(), composite_operation); - auto composited_bottom = composite_value(property_id, underlying_border_image_slice_value.bottom(), animated_border_image_slice_value.bottom(), composite_operation); - auto composited_left = composite_value(property_id, underlying_border_image_slice_value.left(), animated_border_image_slice_value.left(), composite_operation); - if (!composited_top || !composited_right || !composited_bottom || !composited_left) - return {}; - return BorderImageSliceStyleValue::create(composited_top.release_nonnull(), composited_right.release_nonnull(), composited_bottom.release_nonnull(), composited_left.release_nonnull(), underlying_border_image_slice_value.fill()); - } - case StyleValue::Type::BorderRadius: { - auto composited_horizontal_radius = composite_value(property_id, underlying_value.as_border_radius().horizontal_radius(), animated_value.as_border_radius().horizontal_radius(), composite_operation); - auto composited_vertical_radius = composite_value(property_id, underlying_value.as_border_radius().vertical_radius(), animated_value.as_border_radius().vertical_radius(), composite_operation); - if (!composited_horizontal_radius || !composited_vertical_radius) - return {}; - return BorderRadiusStyleValue::create(composited_horizontal_radius.release_nonnull(), composited_vertical_radius.release_nonnull()); - } - case StyleValue::Type::BorderRadiusRect: { - auto const& underlying_top_left = underlying_value.as_border_radius_rect().top_left(); - auto const& animated_top_left = animated_value.as_border_radius_rect().top_left(); - - auto const& underlying_top_right = underlying_value.as_border_radius_rect().top_right(); - auto const& animated_top_right = animated_value.as_border_radius_rect().top_right(); - auto const& underlying_bottom_right = underlying_value.as_border_radius_rect().bottom_right(); - auto const& animated_bottom_right = animated_value.as_border_radius_rect().bottom_right(); - - auto const& underlying_bottom_left = underlying_value.as_border_radius_rect().bottom_left(); - auto const& animated_bottom_left = animated_value.as_border_radius_rect().bottom_left(); - - auto composited_top_left = composite_value(property_id, underlying_top_left, animated_top_left, composite_operation); - auto composited_top_right = composite_value(property_id, underlying_top_right, animated_top_right, composite_operation); - auto composited_bottom_right = composite_value(property_id, underlying_bottom_right, animated_bottom_right, composite_operation); - auto composited_bottom_left = composite_value(property_id, underlying_bottom_left, animated_bottom_left, composite_operation); - - if (!composited_top_left || !composited_top_right || !composited_bottom_right || !composited_bottom_left) - return {}; - - return BorderRadiusRectStyleValue::create(composited_top_left.release_nonnull(), composited_top_right.release_nonnull(), composited_bottom_right.release_nonnull(), composited_bottom_left.release_nonnull()); - } - case StyleValue::Type::Edge: { - auto const& underlying_offset = underlying_value.as_edge().offset(); - auto const& animated_offset = animated_value.as_edge().offset(); - - if (auto composited_value = composite_value(property_id, underlying_offset, animated_offset, composite_operation)) - return EdgeStyleValue::create({}, composited_value); - - return {}; - } - case StyleValue::Type::Flex: { - auto result = composite_raw_values(underlying_value.as_flex().flex().to_fr(), animated_value.as_flex().flex().to_fr()); - return FlexStyleValue::create(Flex::make_fr(result)); - } - case StyleValue::Type::Function: { - auto const& underlying_function = underlying_value.as_function(); - auto const& animated_function = animated_value.as_function(); - - if (underlying_function.name() != animated_function.name()) - return {}; - - auto composited_value = composite_value(property_id, underlying_function.value(), animated_function.value(), composite_operation); - if (!composited_value) - return {}; - - return FunctionStyleValue::create(underlying_function.name(), composited_value.release_nonnull()); - } - case StyleValue::Type::GridTrackSizeList: { - auto underlying_list = underlying_value.as_grid_track_size_list().grid_track_size_list(); - auto animated_list = animated_value.as_grid_track_size_list().grid_track_size_list(); - auto composited_list = composite_grid_track_size_list(property_id, calculation_context, underlying_list, animated_list, composite_operation); - if (!composited_list.has_value()) - return {}; - return GridTrackSizeListStyleValue::create(composited_list.release_value()); - } - case StyleValue::Type::Integer: { - auto result = composite_raw_values(underlying_value.as_integer().integer(), animated_value.as_integer().integer()); - return IntegerStyleValue::create(result); - } - case StyleValue::Type::Length: { - auto result = composite_dimension_value(underlying_value, animated_value); - if (!result.has_value()) - return {}; - VERIFY(underlying_value.as_length().length().unit() == animated_value.as_length().length().unit()); - return LengthStyleValue::create(Length { *result, underlying_value.as_length().length().unit() }); - } - case StyleValue::Type::Number: { - auto result = composite_raw_values(underlying_value.as_number().number(), animated_value.as_number().number()); - return NumberStyleValue::create(result); - } - case StyleValue::Type::OpenTypeTagged: { - auto& underlying_open_type_tagged = underlying_value.as_open_type_tagged(); - auto& animated_open_type_tagged = animated_value.as_open_type_tagged(); - if (underlying_open_type_tagged.tag() != animated_open_type_tagged.tag()) - return {}; - auto composited_value = composite_value(property_id, underlying_open_type_tagged.value(), animated_open_type_tagged.value(), composite_operation); - if (!composited_value) - return {}; - return OpenTypeTaggedStyleValue::create(OpenTypeTaggedStyleValue::Mode::FontVariationSettings, underlying_open_type_tagged.tag(), composited_value.release_nonnull()); - } - case StyleValue::Type::Percentage: { - auto result = composite_raw_values(underlying_value.as_percentage().percentage().value(), animated_value.as_percentage().percentage().value()); - return PercentageStyleValue::create(Percentage { result }); - } - case StyleValue::Type::Position: { - auto& underlying_position = underlying_value.as_position(); - auto& animated_position = animated_value.as_position(); - auto composited_edge_x = composite_value(property_id, underlying_position.edge_x(), animated_position.edge_x(), composite_operation); - auto composited_edge_y = composite_value(property_id, underlying_position.edge_y(), animated_position.edge_y(), composite_operation); - if (!composited_edge_x || !composited_edge_y) - return {}; - - return PositionStyleValue::create(composited_edge_x->as_edge(), composited_edge_y->as_edge()); - } - case StyleValue::Type::RadialSize: { - auto const& underlying_components = underlying_value.as_radial_size().components(); - auto const& animated_components = animated_value.as_radial_size().components(); - - auto const is_radial_extent = [](auto const& component) { return component.template has(); }; - - // https://drafts.csswg.org/css-images-4/#interpolating-gradients - // https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation - // FIXME: Radial extents should disallow composition for basic-shape values but should be converted into their - // equivalent length-percentage values for radial gradients - if (any_of(underlying_components, is_radial_extent) || any_of(animated_components, is_radial_extent)) - return {}; - - if (underlying_components.size() == 1 && animated_components.size() == 1) { - auto const& underlying_component = underlying_components[0].get>(); - auto const& animated_component = animated_components[0].get>(); - - auto interpolated_value = composite_value(property_id, underlying_component, animated_component, composite_operation); - if (!interpolated_value) - return {}; - - return RadialSizeStyleValue::create({ interpolated_value.release_nonnull() }); - } - - auto const& underlying_horizontal_component = underlying_components[0].get>(); - auto const& underlying_vertical_component = underlying_components.size() > 1 ? underlying_components[1].get>() : underlying_horizontal_component; - - auto const& animated_horizontal_component = animated_components[0].get>(); - auto const& animated_vertical_component = animated_components.size() > 1 ? animated_components[1].get>() : animated_horizontal_component; - auto composited_horizontal = composite_value(property_id, underlying_horizontal_component, animated_horizontal_component, composite_operation); - auto composited_vertical = composite_value(property_id, underlying_vertical_component, animated_vertical_component, composite_operation); - - if (!composited_horizontal || !composited_vertical) - return {}; - - return RadialSizeStyleValue::create({ composited_horizontal.release_nonnull(), composited_vertical.release_nonnull() }); - } - case StyleValue::Type::Ratio: { - // https://drafts.csswg.org/css-values/#combine-ratio - // Addition of s is not possible. - return {}; - } - case StyleValue::Type::ValueList: { - auto& underlying_list = underlying_value.as_value_list(); - auto& animated_list = animated_value.as_value_list(); - - if (is_filter_style_value_list(underlying_value) && is_filter_style_value_list(animated_value)) { - // https://drafts.csswg.org/filter-effects/#addition - // Given two filter values representing an base value (base filter list) and a value to add (added filter list), - // returns the concatenation of the the two lists: ‘base filter list added filter list’. - if (composite_operation == Bindings::CompositeOperation::Add) { - StyleValueVector result { underlying_list.values() }; - result.extend(StyleValueVector { animated_list.values() }); - return StyleValueList::create(move(result), StyleValueList::Separator::Space, StyleValueList::Collapsible::No); - } - - VERIFY(composite_operation == Bindings::CompositeOperation::Accumulate); - auto result = accumulate_filter_function(underlying_list, animated_list, color_resolution_context); - if (result.is_empty()) - return {}; - - return StyleValueList::create(move(result), StyleValueList::Separator::Space, StyleValueList::Collapsible::No); - } - - if (underlying_list.size() != animated_list.size() || underlying_list.separator() != animated_list.separator()) - return {}; - StyleValueVector values; - values.ensure_capacity(underlying_list.size()); - for (size_t i = 0; i < underlying_list.size(); ++i) { - auto composited_value = composite_value(property_id, underlying_list.values()[i], animated_list.values()[i], composite_operation); - if (!composited_value) - return {}; - values.unchecked_append(*composited_value); - } - return StyleValueList::create(move(values), underlying_list.separator()); - } - default: - // FIXME: Implement compositing for missing types - return {}; - } -} - -} diff --git a/Libraries/LibWeb/CSS/Interpolation.h b/Libraries/LibWeb/CSS/Interpolation.h deleted file mode 100644 index 065e5a65b262c..0000000000000 --- a/Libraries/LibWeb/CSS/Interpolation.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2024, Sam Atkins - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace Web::CSS { - -enum class AllowDiscrete { - Yes, - No, -}; -ValueComparingRefPtr interpolate_property(DOM::Element&, PropertyID, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete, ColorResolutionContext const* = nullptr); - -// https://drafts.csswg.org/css-transitions/#transitionable -bool property_values_are_transitionable(PropertyID, StyleValue const& old_value, StyleValue const& new_value, DOM::Element&, TransitionBehavior); - -RefPtr interpolate_value(DOM::Element&, CalculationContext const&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete, ColorResolutionContext const* = nullptr); -RefPtr interpolate_repeatable_list(DOM::Element&, CalculationContext const&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete, ColorResolutionContext const* = nullptr); -RefPtr interpolate_box_shadow(DOM::Element&, CalculationContext const&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete); -RefPtr interpolate_transform(DOM::Element&, CalculationContext const&, StyleValue const& from, StyleValue const& to, float delta, AllowDiscrete); - -RefPtr interpolate_color(StyleValue const& from, StyleValue const& to, float delta, Optional, ColorResolutionContext const&); - -RefPtr composite_value(PropertyID, StyleValue const& a_underlying_value, StyleValue const& a_animated_value, Bindings::CompositeOperation, ColorResolutionContext const& = {}); - -} diff --git a/Libraries/LibWeb/CSS/InvalidationSet.h b/Libraries/LibWeb/CSS/InvalidationSet.h index b3fa8941be074..2bfcccaef51bd 100644 --- a/Libraries/LibWeb/CSS/InvalidationSet.h +++ b/Libraries/LibWeb/CSS/InvalidationSet.h @@ -42,7 +42,6 @@ class InvalidationSet { void include_all_from(InvalidationSet const& other); - bool needs_invalidate_self() const { return m_needs_invalidate_self; } void set_needs_invalidate_self() { m_needs_invalidate_self = true; diff --git a/Libraries/LibWeb/CSS/Length.h b/Libraries/LibWeb/CSS/Length.h index 24e98a80bbdf3..dfb82926b596e 100644 --- a/Libraries/LibWeb/CSS/Length.h +++ b/Libraries/LibWeb/CSS/Length.h @@ -53,8 +53,6 @@ class WEB_API Length { bool is_font_relative() const { return CSS::is_font_relative(m_unit); } bool is_container_relative() const { return CSS::is_container_relative(m_unit); } bool is_viewport_relative() const { return CSS::is_viewport_relative(m_unit); } - bool is_relative() const { return CSS::is_relative(m_unit); } - bool is_computationally_independent() const { return !is_font_relative() && !is_container_relative(); } double raw_value() const { return m_value; } LengthUnit unit() const { return m_unit; } diff --git a/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Libraries/LibWeb/CSS/Parser/Parser.cpp index 7f7185fbec2e5..8d6216f9819ce 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -1740,18 +1740,6 @@ Vector Parser::parse_a_blocks_contents(TokenStream& return consume_a_blocks_contents(input); } -Optional Parser::parse_as_supports_condition() -{ - m_rule_context.append(RuleContext::SupportsCondition); - auto maybe_declaration = parse_a_declaration(m_token_stream); - m_rule_context.take_last(); - if (maybe_declaration.has_value()) { - if (auto maybe_property_and_name = convert_to_style_property(maybe_declaration.release_value()); maybe_property_and_name.has_value()) - return maybe_property_and_name->property; - } - return {}; -} - // https://drafts.csswg.org/css-syntax/#parse-declaration template Optional Parser::parse_a_declaration(TokenStream& input) diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 1dec192546579..e2d257c30d19f 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -151,7 +151,6 @@ class Parser { CSSRule* parse_as_css_rule(bool nested = false); GC::Ptr parse_as_keyframe_rule(); Vector parse_as_keyframe_selectors(); - Optional parse_as_supports_condition(); GC::RootVector> parse_as_stylesheet_contents(); enum class SelectorParsingMode { @@ -365,7 +364,6 @@ class Parser { RefPtr parse_source_size_value(TokenStream&); Optional parse_unicode_range(TokenStream&); Optional parse_unicode_range(StringView); - Vector parse_unicode_ranges(TokenStream&); RefPtr parse_unicode_range_value(TokenStream&); RefPtr parse_value(ValueType, TokenStream&); diff --git a/Libraries/LibWeb/CSS/Parser/Types.cpp b/Libraries/LibWeb/CSS/Parser/Types.cpp index 16e6b64c2bc31..c6426cc590ef6 100644 --- a/Libraries/LibWeb/CSS/Parser/Types.cpp +++ b/Libraries/LibWeb/CSS/Parser/Types.cpp @@ -204,26 +204,6 @@ void AtRule::for_each_as_qualified_rule_list(QualifiedRuleVisitor&& visit) const }); } -// https://drafts.csswg.org/css-syntax/#typedef-at-rule-list -void AtRule::for_each_as_at_rule_list(AtRuleVisitor&& visit) const -{ - // : only at-rules are allowed; declarations and qualified rules are automatically invalid. - for_each( - move(visit), - [this](auto const&) { - ErrorReporter::the().report(InvalidRuleLocationError { - .outer_rule_name = Utf16String::formatted("@{}", name), - .inner_rule_name = "qualified-rule"_utf16_fly_string, - }); - }, - [this](auto const&) { - ErrorReporter::the().report(InvalidRuleLocationError { - .outer_rule_name = Utf16String::formatted("@{}", name), - .inner_rule_name = "list-of-declarations"_utf16_fly_string, - }); - }); -} - // https://drafts.csswg.org/css-syntax/#typedef-declaration-rule-list void AtRule::for_each_as_declaration_rule_list(AtRuleVisitor&& visit_at_rule, DeclarationVisitor&& visit_declaration) const { @@ -239,22 +219,6 @@ void AtRule::for_each_as_declaration_rule_list(AtRuleVisitor&& visit_at_rule, De move(visit_declaration)); } -// https://drafts.csswg.org/css-syntax/#typedef-rule-list -void AtRule::for_each_as_rule_list(RuleVisitor&& visit) const -{ - // : qualified rules and at-rules are allowed; declarations are automatically invalid. - for (auto const& child : child_rules_and_lists_of_declarations) { - child.visit( - [&](Rule const& rule) { visit(rule); }, - [&](Vector const&) { - ErrorReporter::the().report(InvalidRuleLocationError { - .outer_rule_name = Utf16String::formatted("@{}", name), - .inner_rule_name = "list-of-declarations"_utf16_fly_string, - }); - }); - } -} - // https://drafts.csswg.org/css-syntax/#typedef-declaration-list void QualifiedRule::for_each_as_declaration_list(Utf16FlyString const& rule_name, DeclarationVisitor&& visit) const { diff --git a/Libraries/LibWeb/CSS/Parser/Types.h b/Libraries/LibWeb/CSS/Parser/Types.h index 4d672f307e38a..18f0099e1d11e 100644 --- a/Libraries/LibWeb/CSS/Parser/Types.h +++ b/Libraries/LibWeb/CSS/Parser/Types.h @@ -37,9 +37,7 @@ struct AtRule { void for_each(AtRuleVisitor&& visit_at_rule, QualifiedRuleVisitor&& visit_qualified_rule, DeclarationVisitor&& visit_declaration) const; void for_each_as_declaration_list(DeclarationVisitor&& visit) const; void for_each_as_qualified_rule_list(QualifiedRuleVisitor&& visit) const; - void for_each_as_at_rule_list(AtRuleVisitor&& visit) const; void for_each_as_declaration_rule_list(AtRuleVisitor&& visit_at_rule, DeclarationVisitor&& visit_declaration) const; - void for_each_as_rule_list(RuleVisitor&& visit) const; }; // https://drafts.csswg.org/css-syntax/#qualified-rule diff --git a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp index 6841aee34a358..21271141dfbfb 100644 --- a/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/ValueParsing.cpp @@ -578,20 +578,6 @@ Optional Parser::parse_unicode_range(StringView text) return make_valid_unicode_range(start_value, end_value); } -Vector Parser::parse_unicode_ranges(TokenStream& tokens) -{ - Vector unicode_ranges; - auto range_token_lists = parse_a_comma_separated_list_of_component_values(tokens); - for (auto& range_tokens : range_token_lists) { - TokenStream range_token_stream { range_tokens }; - auto maybe_unicode_range = parse_unicode_range(range_token_stream); - if (!maybe_unicode_range.has_value()) - return {}; - unicode_ranges.append(maybe_unicode_range.release_value()); - } - return unicode_ranges; -} - RefPtr Parser::parse_unicode_range_value(TokenStream& tokens) { if (auto range = parse_unicode_range(tokens); range.has_value()) diff --git a/Libraries/LibWeb/CSS/PercentageOr.h b/Libraries/LibWeb/CSS/PercentageOr.h index 601496ce9f834..92245f0634488 100644 --- a/Libraries/LibWeb/CSS/PercentageOr.h +++ b/Libraries/LibWeb/CSS/PercentageOr.h @@ -27,46 +27,39 @@ namespace Web::CSS { class LengthPercentage { public: LengthPercentage(Length t) - : m_value(move(t)) + : m_value(StyleValueFFI::rust_style_value_create_length(t.raw_value(), to_underlying(t.unit()))) { } LengthPercentage(Percentage percentage) - : m_value(move(percentage)) + : m_value(StyleValueFFI::rust_style_value_create_percentage(percentage.value())) { } LengthPercentage(NonnullRefPtr calculated) - : m_value(move(calculated)) + : m_value(StyleValueFFI::rust_style_value_retain(calculated->rust_style_value_data())) { } - ~LengthPercentage() = default; - bool contains_percentage() const { - return m_value.visit( - [&](Length const&) { - return false; - }, - [&](Percentage const&) { - return true; - }, - [&](NonnullRefPtr const& calculated) { - return calculated->contains_percentage(); - }); + if (is_percentage()) + return true; + if (is_calculated()) + return calculated()->contains_percentage(); + return false; } - Percentage const& percentage() const + Percentage percentage() const { VERIFY(is_percentage()); - return m_value.template get(); + return Percentage(m_value->percentage.value); } - NonnullRefPtr const& calculated() const + ValueComparingNonnullRefPtr calculated() const { VERIFY(is_calculated()); - return m_value.template get>(); + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(m_value.data()))->as_calculated(); } CSSPixels to_px(CSSPixels reference_value) const @@ -76,26 +69,21 @@ class LengthPercentage { Length resolved(CSSPixels reference_value) const { - return m_value.visit( - [&](Length const& t) { - return t; - }, - [&](Percentage const& percentage) { - return Length::make_px(CSSPixels::truncated_value_for(reference_value.to_double() * percentage.as_fraction())); - }, - [&](NonnullRefPtr const& calculated) { - return calculated->resolve_length({ .percentage_basis = Length::make_px(reference_value) }).value(); - }); + if (is_length()) + return length(); + if (is_percentage()) + return Length::make_px(CSSPixels::truncated_value_for(reference_value.to_double() * percentage().as_fraction())); + return calculated()->resolve_length({ .percentage_basis = Length::make_px(reference_value) }).value(); } void serialize(StringBuilder& builder, SerializationMode mode) const { if (is_calculated()) { - m_value.template get>()->serialize(builder, mode); + calculated()->serialize(builder, mode); } else if (is_percentage()) { - m_value.template get().serialize(builder, mode); + percentage().serialize(builder, mode); } else { - m_value.template get().serialize(builder, mode); + length().serialize(builder, mode); } } @@ -108,25 +96,54 @@ class LengthPercentage { static LengthPercentage from_style_value(NonnullRefPtr const& style_value) { - if (style_value->is_percentage()) - return LengthPercentage { style_value->as_percentage().percentage() }; - if (style_value->is_length()) - return LengthPercentage { style_value->as_length().length() }; - if (style_value->is_calculated()) - return LengthPercentage { style_value->as_calculated() }; + VERIFY(style_value->is_percentage() || style_value->is_length() || style_value->is_calculated()); + return from_retained_data(StyleValueFFI::rust_style_value_retain(style_value->rust_style_value_data())); + } - VERIFY_NOT_REACHED(); + bool is_percentage() const { return m_value->tag == StyleValueFFI::StyleValueData::Tag::Percentage; } + bool is_calculated() const { return m_value->tag == StyleValueFFI::StyleValueData::Tag::Calculated; } + bool is_length() const { return m_value->tag == StyleValueFFI::StyleValueData::Tag::Length; } + Length length() const + { + VERIFY(is_length()); + return Length(m_value->length.value, static_cast(m_value->length.unit)); } - bool is_percentage() const { return m_value.template has(); } - bool is_calculated() const { return m_value.template has>(); } - bool is_length() const { return m_value.has(); } - Length const& length() const { return m_value.get(); } + bool operator==(LengthPercentage const& other) const + { + if (m_value.data() == other.m_value.data()) + return true; + if (is_length() && other.is_length()) + return length() == other.length(); + if (is_percentage() && other.is_percentage()) + return percentage() == other.percentage(); + if (is_calculated() && other.is_calculated()) + return calculated()->equals(*other.calculated()); + return false; + } + + template + static LengthPercentage const& view(Handle const& value) + { + static_assert(sizeof(LengthPercentage) == sizeof(value)); + static_assert(requires { value.pointer; }); + return reinterpret_cast(value); + } - bool operator==(LengthPercentage const& other) const = default; + StyleValueFFI::StyleValueData const* leak_data() { return m_value.leak_data(); } private: - Variant> m_value; + explicit LengthPercentage(StyleValueFFI::StyleValueData const* data) + : m_value(data) + { + } + + static LengthPercentage from_retained_data(StyleValueFFI::StyleValueData const* data) + { + return LengthPercentage(data); + } + + RustStyleValueHandle m_value; }; class LengthPercentageOrAuto { @@ -167,9 +184,9 @@ class LengthPercentageOrAuto { bool contains_percentage() const { return m_length_percentage.has_value() && m_length_percentage->contains_percentage(); } LengthPercentage const& length_percentage() const { return m_length_percentage.value(); } - Length const& length() const { return m_length_percentage->length(); } - Percentage const& percentage() const { return m_length_percentage->percentage(); } - NonnullRefPtr const& calculated() const { return m_length_percentage->calculated(); } + Length length() const { return m_length_percentage->length(); } + Percentage percentage() const { return m_length_percentage->percentage(); } + ValueComparingNonnullRefPtr calculated() const { return m_length_percentage->calculated(); } LengthOrAuto resolved_or_auto(CSSPixels reference_value) const { diff --git a/Libraries/LibWeb/CSS/PreferredContrast.cpp b/Libraries/LibWeb/CSS/PreferredContrast.cpp deleted file mode 100644 index 8410319d4f075..0000000000000 --- a/Libraries/LibWeb/CSS/PreferredContrast.cpp +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2024-present, the Ladybird developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Web::CSS { - -PreferredContrast preferred_contrast_from_string(Utf16View value) -{ - if (value.equals_ignoring_ascii_case(u"less"sv)) - return PreferredContrast::Less; - if (value.equals_ignoring_ascii_case(u"more"sv)) - return PreferredContrast::More; - if (value.equals_ignoring_ascii_case(u"no-preference"sv)) - return PreferredContrast::NoPreference; - return PreferredContrast::Auto; -} - -} diff --git a/Libraries/LibWeb/CSS/PreferredContrast.h b/Libraries/LibWeb/CSS/PreferredContrast.h index d18b7e4db341e..af8aede882761 100644 --- a/Libraries/LibWeb/CSS/PreferredContrast.h +++ b/Libraries/LibWeb/CSS/PreferredContrast.h @@ -6,8 +6,6 @@ #pragma once -#include - namespace Web::CSS { enum class PreferredContrast { @@ -17,6 +15,4 @@ enum class PreferredContrast { NoPreference, }; -PreferredContrast preferred_contrast_from_string(Utf16View); - } diff --git a/Libraries/LibWeb/CSS/PreferredMotion.cpp b/Libraries/LibWeb/CSS/PreferredMotion.cpp deleted file mode 100644 index 3310dbfed7f73..0000000000000 --- a/Libraries/LibWeb/CSS/PreferredMotion.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2024-present, the Ladybird developers. - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include - -namespace Web::CSS { - -PreferredMotion preferred_motion_from_string(Utf16View value) -{ - if (value.equals_ignoring_ascii_case(u"no-preference"sv)) - return PreferredMotion::NoPreference; - if (value.equals_ignoring_ascii_case(u"reduce"sv)) - return PreferredMotion::Reduce; - return PreferredMotion::Auto; -} - -} diff --git a/Libraries/LibWeb/CSS/PreferredMotion.h b/Libraries/LibWeb/CSS/PreferredMotion.h index 608f4260e9b3c..a46b899e4bae2 100644 --- a/Libraries/LibWeb/CSS/PreferredMotion.h +++ b/Libraries/LibWeb/CSS/PreferredMotion.h @@ -6,8 +6,6 @@ #pragma once -#include - namespace Web::CSS { enum class PreferredMotion { @@ -16,6 +14,4 @@ enum class PreferredMotion { Reduce, }; -PreferredMotion preferred_motion_from_string(Utf16View); - } diff --git a/Libraries/LibWeb/CSS/PseudoElementPropertyGroups.txt b/Libraries/LibWeb/CSS/PseudoElementPropertyGroups.txt new file mode 100644 index 0000000000000..146c577529bd0 --- /dev/null +++ b/Libraries/LibWeb/CSS/PseudoElementPropertyGroups.txt @@ -0,0 +1,181 @@ +# Copyright (c) 2026-present, the Ladybird developers. +# SPDX-License-Identifier: BSD-2-Clause + +[always-allowed-properties] +transition +transition-behavior +transition-delay +transition-duration +transition-property +transition-timing-function +animation +animation-composition +animation-delay +animation-direction +animation-duration +animation-fill-mode +animation-iteration-count +animation-name +animation-play-state +animation-timeline +animation-timing-function + +[background-properties] +# https://drafts.csswg.org/css-backgrounds/#property-index +background +background-attachment +background-blend-mode +background-clip +background-color +background-image +background-origin +background-position +background-position-x +background-position-y +background-repeat +background-size + +[border-properties] +# https://drafts.csswg.org/css-backgrounds/#property-index +border +border-block-end +border-block-end-color +border-block-end-style +border-block-end-width +border-block-start +border-block-start-color +border-block-start-style +border-block-start-width +border-bottom +border-bottom-color +border-bottom-left-radius +border-bottom-right-radius +border-bottom-style +border-bottom-width +border-color +border-image +border-image-outset +border-image-repeat +border-image-slice +border-image-source +border-image-width +border-inline-end +border-inline-end-color +border-inline-end-style +border-inline-end-width +border-inline-start +border-inline-start-color +border-inline-start-style +border-inline-start-width +border-left +border-left-color +border-left-style +border-left-width +border-radius +border-right +border-right-color +border-right-style +border-right-width +border-style +border-top +border-top-color +border-top-left-radius +border-top-right-radius +border-top-style +border-top-width +border-width + +[custom-properties] +custom + +[font-properties] +# https://drafts.csswg.org/css-fonts/#property-index +# FIXME: font-palette +# FIXME: font-size-adjust +# FIXME: font-synthesis and longhands +font +font-family +font-feature-settings +font-kerning +font-language-override +font-optical-sizing +font-size +font-style +font-variant +font-variant-alternates +font-variant-caps +font-variant-east-asian +font-variant-emoji +font-variant-ligatures +font-variant-numeric +font-variant-position +font-variation-settings +font-weight +font-width + +[inline-layout-properties] +# https://drafts.csswg.org/css-inline/#property-index +# FIXME: alignment-baseline +# FIXME: baseline-shift +# FIXME: baseline-source +# FIXME: dominant-baseline +# FIXME: initial-letter +# FIXME: initial-letter-align +# FIXME: initial-letter-wrap +# FIXME: inline-sizing +# FIXME: line-edge-fit +# FIXME: text-box +# FIXME: text-box-edge +# FIXME: text-box-trim +line-height +vertical-align + +[inline-typesetting-properties] +# https://drafts.csswg.org/css-text-4/#property-index +# FIXME: hanging-punctuation +# FIXME: line-padding +# FIXME: text-autospace +# FIXME: text-spacing +# FIXME: text-spacing-trim +# FIXME: word-space-transform +letter-spacing +text-justify +text-transform +word-spacing + +[margin-properties] +margin +margin-block +margin-block-end +margin-block-start +margin-bottom +margin-inline +margin-inline-end +margin-inline-start +margin-left +margin-right +margin-top + +[padding-properties] +padding +padding-block +padding-block-end +padding-block-start +padding-bottom +padding-inline +padding-inline-end +padding-inline-start +padding-left +padding-right +padding-top + +[text-decoration-properties] +text-decoration +text-decoration-color +text-decoration-line +text-decoration-skip-ink +text-decoration-style +text-decoration-thickness +text-shadow +text-underline-offset +text-underline-position diff --git a/Libraries/LibWeb/CSS/Rust/build.rs b/Libraries/LibWeb/CSS/Rust/build.rs index eac97a573d792..77933e25ec3e4 100644 --- a/Libraries/LibWeb/CSS/Rust/build.rs +++ b/Libraries/LibWeb/CSS/Rust/build.rs @@ -22,6 +22,53 @@ fn title_casify(dashy_name: &str) -> String { .collect() } +fn ordered_pseudo_element_names( + pseudo_elements: &serde_json::Map, +) -> Result, Box> { + let mut synthetic = Vec::new(); + let mut element_reference = Vec::new(); + let mut functional = Vec::new(); + for (name, value) in pseudo_elements { + let object = value.as_object().unwrap(); + if object.contains_key("alias-for") { + continue; + } + if object.get("type").and_then(|value| value.as_str()) == Some("function") { + functional.push(name.clone()); + continue; + } + match object.get("implementation").and_then(|value| value.as_str()) { + Some("synthetic") => synthetic.push(name.clone()), + Some("element-reference") => element_reference.push(name.clone()), + other => return Err(format!("invalid or missing implementation type for ::{name}: {other:?}").into()), + } + } + synthetic.extend(element_reference); + synthetic.extend(functional); + Ok(synthetic) +} + +fn load_property_groups(path: &Path) -> Result>, Box> { + let mut groups = std::collections::HashMap::new(); + let mut current_group = None; + for raw_line in std::fs::read_to_string(path)?.lines() { + let line = raw_line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some(group) = line.strip_prefix('[').and_then(|line| line.strip_suffix(']')) { + groups.insert(format!("#{group}"), Vec::new()); + current_group = Some(format!("#{group}")); + continue; + } + let Some(group) = current_group.as_ref() else { + return Err(format!("property outside a pseudo-element property group: {line}").into()); + }; + groups.get_mut(group).unwrap().push(line.to_string()); + } + Ok(groups) +} + fn write_enum_and_from_ffi(output: &mut String, enum_name: &str, variants: &[String]) { writeln!(output, "#[derive(Clone, Copy, Debug, PartialEq, Eq)]").unwrap(); writeln!(output, "#[repr(u8)]").unwrap(); @@ -71,27 +118,11 @@ fn generate_selector_pseudo_types(manifest_dir: &Path, out_dir: &Path) -> Result .map(|(name, _)| title_casify(name)) .collect::>(); - let mut synthetic_pseudo_elements = Vec::new(); - let mut element_reference_pseudo_elements = Vec::new(); - let mut functional_pseudo_elements = Vec::new(); - for (name, value) in &parse_object(&pseudo_elements_path)? { - let object = value.as_object().unwrap(); - if object.contains_key("alias-for") { - continue; - } - if object.get("type").and_then(|value| value.as_str()) == Some("function") { - functional_pseudo_elements.push(title_casify(name)); - continue; - } - match object.get("implementation").and_then(|value| value.as_str()) { - Some("synthetic") => synthetic_pseudo_elements.push(title_casify(name)), - Some("element-reference") => element_reference_pseudo_elements.push(title_casify(name)), - other => return Err(format!("invalid or missing implementation type for ::{name}: {other:?}").into()), - } - } - let mut pseudo_element_names = synthetic_pseudo_elements; - pseudo_element_names.extend(element_reference_pseudo_elements); - pseudo_element_names.extend(functional_pseudo_elements); + let pseudo_elements = parse_object(&pseudo_elements_path)?; + let mut pseudo_element_names = ordered_pseudo_element_names(&pseudo_elements)? + .iter() + .map(|name| title_casify(name)) + .collect::>(); // NB: The C++ generator emits UnknownWebKit after KnownPseudoElementCount, so its FFI value is // the number of known pseudo-elements. pseudo_element_names.push("UnknownWebKit".to_string()); @@ -178,6 +209,66 @@ fn generate_property_metadata(manifest_dir: &Path, out_dir: &Path) -> Result<(), let first_inherited = ids[&inherited_longhands[0]]; let last_inherited = ids[inherited_longhands.last().unwrap()]; + let pseudo_elements_path = manifest_dir.parent().unwrap().join("PseudoElements.json"); + let property_groups_path = manifest_dir.parent().unwrap().join("PseudoElementPropertyGroups.txt"); + println!("cargo:rerun-if-changed={}", pseudo_elements_path.display()); + println!("cargo:rerun-if-changed={}", property_groups_path.display()); + let pseudo_elements_value: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&pseudo_elements_path)?)?; + let serde_json::Value::Object(pseudo_elements) = pseudo_elements_value else { + return Err("PseudoElements.json does not contain a JSON object".into()); + }; + let property_groups = load_property_groups(&property_groups_path)?; + let property_id = |name: &str| -> Result> { + if name == "custom" { + return Ok(0); + } + ids.get(name) + .copied() + .ok_or_else(|| format!("unknown pseudo-element property '{name}'").into()) + }; + let property_ids = |entries: &[String]| -> Result, Box> { + let mut result = std::collections::BTreeSet::new(); + for entry in entries { + if entry.starts_with("FIXME:") { + continue; + } + if let Some(properties) = property_groups.get(entry) { + for property in properties { + result.insert(property_id(property)?); + } + } else if entry.starts_with('#') { + return Err(format!("unknown pseudo-element property group '{entry}'").into()); + } else { + result.insert(property_id(entry)?); + } + } + Ok(result.into_iter().collect()) + }; + let always_allowed_pseudo_properties = property_ids( + property_groups + .get("#always-allowed-properties") + .ok_or("missing always-allowed pseudo-element property group")?, + )?; + let mut pseudo_property_whitelist_rows = Vec::new(); + for name in ordered_pseudo_element_names(&pseudo_elements)? { + let object = pseudo_elements[&name].as_object().unwrap(); + let Some(whitelist) = object.get("property-whitelist") else { + pseudo_property_whitelist_rows.push(" None,".to_string()); + continue; + }; + let entries = whitelist + .as_array() + .unwrap() + .iter() + .map(|entry| entry.as_str().unwrap().to_string()) + .collect::>(); + let whitelist = property_ids(&entries)?; + pseudo_property_whitelist_rows.push(format!(" Some(&{whitelist:?}),")); + } + // UnknownWebKit follows the known pseudo-elements and accepts all properties. + pseudo_property_whitelist_rows.push(" None,".to_string()); + // NB: Must match manually_specified_computation_order in // Meta/Generators/generate_libweb_css_property_id.py; the parity test enforces it. let manual_order = [ @@ -222,7 +313,57 @@ fn generate_property_metadata(manifest_dir: &Path, out_dir: &Path) -> Result<(), } let mut levels = vec![0u8; (last_longhand - first_longhand + 1) as usize]; + let mut animation_types = vec![0u8; levels.len()]; + let mut numeric_range_rows = vec![String::new(); levels.len()]; + let value_types = [ + "anchor", + "anchor-size", + "angle", + "angle-percentage", + "background-position", + "basic-shape", + "color", + "corner-shape", + "counter", + "counter-style", + "custom-ident", + "dashed-ident", + "easing-function", + "filter-value-list", + "fit-content", + "flex", + "font-style", + "font-variant-alternates", + "font-variant-east-asian", + "font-variant-ligatures", + "font-variant-numeric", + "frequency", + "frequency-percentage", + "image", + "integer", + "length", + "length-percentage", + "number", + "opacity-value", + "opentype-tag", + "paint", + "percentage", + "position", + "ratio", + "rect", + "resolution", + "scroll-function", + "string", + "time", + "time-percentage", + "transform-function", + "transform-list", + "url", + "view-function", + "view-timeline-inset", + ]; for name in inherited_longhands.iter().chain(&noninherited_longhands) { + let index = (ids[name] - first_longhand) as usize; let requires_computation = property_field(name, "requires-computation").unwrap(); let level = match requires_computation.as_str().unwrap() { "never" => 0u8, @@ -231,11 +372,57 @@ fn generate_property_metadata(manifest_dir: &Path, out_dir: &Path) -> Result<(), "always" => 3, other => return Err(format!("unknown requires-computation '{other}' for {name}").into()), }; - levels[(ids[name] - first_longhand) as usize] = level; + levels[index] = level; + + animation_types[index] = match property_field(name, "animation-type").unwrap().as_str().unwrap() { + "discrete" => 0, + "by-computed-value" => 1, + "repeatable-list" => 2, + "custom" => 3, + "none" => 4, + other => return Err(format!("unknown animation-type '{other}' for {name}").into()), + }; + + let mut ranges = Vec::new(); + if let Some(valid_types) = property_field(name, "valid-types").and_then(|value| value.as_array().cloned()) { + for valid_type in valid_types { + let valid_type = valid_type.as_str().unwrap(); + let Some((type_name, range)) = valid_type.split_once(' ') else { + continue; + }; + if !range.starts_with('[') || !range.ends_with(']') || !range.contains(',') { + continue; + } + if type_name == "custom-ident" { + continue; + } + let value_type = value_types + .iter() + .position(|candidate| *candidate == type_name) + .ok_or_else(|| format!("unknown ranged value type '{type_name}' for {name}"))?; + let (min, max) = range[1..range.len() - 1] + .split_once(',') + .ok_or_else(|| format!("bad numeric range '{range}' for {name}"))?; + let format_bound = |bound: &str| match (type_name, bound) { + ("integer", "-∞") => "i32::MIN as f64".to_string(), + ("integer", "∞") => "i32::MAX as f64".to_string(), + (_, "-∞") => "f32::MIN as f64".to_string(), + (_, "∞") => "f32::MAX as f64".to_string(), + _ if bound.contains('.') => bound.to_string(), + _ => format!("{bound}.0"), + }; + ranges.push(format!( + "FfiPropertyNumericRange {{ value_type: {value_type}, min: {}, max: {} }}", + format_bound(min), + format_bound(max) + )); + } + } + numeric_range_rows[index] = ranges.join(", "); } let mut output = String::new(); - output.push_str("// Generated by build.rs from Properties.json. Do not edit.\n\n"); + output.push_str("// Generated by build.rs from CSS metadata. Do not edit.\n\n"); // Shorthand expansion tables for the cascade. The "all" shorthand expands to every // longhand except direction and unicode-bidi, mirroring the C++ generator. let mut shorthand_rows = Vec::new(); @@ -269,6 +456,88 @@ fn generate_property_metadata(manifest_dir: &Path, out_dir: &Path) -> Result<(), shorthand_rows.len(), shorthand_rows.join("\n") )); + let property_names: Vec<&str> = shorthands + .iter() + .chain(&inherited_longhands) + .chain(&noninherited_longhands) + .map(String::as_str) + .collect(); + fn expanded_longhands( + name: &str, + properties: &serde_json::Map, + all_longhands: &[String], + result: &mut Vec, + ) { + if name == "all" { + result.extend_from_slice(all_longhands); + return; + } + let Some(longhands) = properties[name].get("longhands").and_then(|value| value.as_array()) else { + result.push(name.to_string()); + return; + }; + for longhand in longhands { + expanded_longhands(longhand.as_str().unwrap(), properties, all_longhands, result); + } + } + let all_longhands: Vec = inherited_longhands + .iter() + .chain(&noninherited_longhands) + .filter(|name| name.as_str() != "direction" && name.as_str() != "unicode-bidi") + .cloned() + .collect(); + let expanded_shorthand_longhands: Vec> = shorthands + .iter() + .map(|name| { + let mut result = Vec::new(); + expanded_longhands(name, &properties, &all_longhands, &mut result); + result + }) + .collect(); + let camel_case_property_name = |name: &str| { + let mut parts = name.split('-').filter(|part| !part.is_empty()); + let mut result = parts.next().unwrap_or_default().to_string(); + for part in parts { + let mut characters = part.chars(); + if let Some(first) = characters.next() { + result.extend(first.to_uppercase()); + result.extend(characters); + } + } + result + }; + let idl_names: Vec = property_names + .iter() + .map(|name| camel_case_property_name(name)) + .collect(); + let logical_aliases: Vec = property_names + .iter() + .enumerate() + .map(|(index, name)| { + let name: &str = if index < shorthands.len() { + expanded_shorthand_longhands[index][0].as_str() + } else { + name + }; + properties[name].as_object().unwrap().contains_key("logical-alias-for") + }) + .collect(); + let expanded_longhand_counts: Vec = expanded_shorthand_longhands.iter().map(Vec::len).collect(); + output.push_str(&format!( + "pub(crate) static PROPERTY_IDL_NAMES: [&str; {}] = {:?};\n", + idl_names.len(), + idl_names + )); + output.push_str(&format!( + "pub(crate) static PROPERTY_IS_LOGICAL_ALIAS: [bool; {}] = {:?};\n\n", + logical_aliases.len(), + logical_aliases + )); + output.push_str(&format!( + "pub(crate) static SHORTHAND_EXPANDED_LONGHAND_COUNTS: [usize; {}] = {:?};\n\n", + expanded_longhand_counts.len(), + expanded_longhand_counts + )); output.push_str(&format!( "pub const FIRST_SHORTHAND_PROPERTY_ID: u16 = {};\n", ids[&shorthands[0]] @@ -310,6 +579,28 @@ fn generate_property_metadata(manifest_dir: &Path, out_dir: &Path) -> Result<(), levels.len(), levels )); + output.push_str(&format!( + "pub(crate) static PROPERTY_ANIMATION_TYPES: [u8; {}] = {:?};\n", + animation_types.len(), + animation_types + )); + output.push_str(&format!( + "pub(crate) static PROPERTY_NUMERIC_RANGES: [&[FfiPropertyNumericRange]; {}] = [\n{}\n];\n", + numeric_range_rows.len(), + numeric_range_rows + .iter() + .map(|ranges| format!(" &[{ranges}],")) + .collect::>() + .join("\n") + )); + output.push_str(&format!( + "\npub(crate) static PSEUDO_ELEMENT_ALWAYS_ALLOWED_PROPERTIES: &[u16] = &{always_allowed_pseudo_properties:?};\n" + )); + output.push_str(&format!( + "pub(crate) static PSEUDO_ELEMENT_PROPERTY_WHITELISTS: [Option<&[u16]>; {}] = [\n{}\n];\n", + pseudo_property_whitelist_rows.len(), + pseudo_property_whitelist_rows.join("\n") + )); std::fs::write(out_dir.join("property_metadata_generated.rs"), output)?; Ok(()) } @@ -599,6 +890,9 @@ fn main() -> Result<(), Box> { style_value_config, &[ manifest_dir.join("src/style_value.rs"), + manifest_dir.join("src/color_interpolation.rs"), + manifest_dir.join("src/animation.rs"), + manifest_dir.join("src/transition.rs"), manifest_dir.join("src/calc.rs"), manifest_dir.join("src/ffi_stats.rs"), ], diff --git a/Libraries/LibWeb/CSS/Rust/src/animation.rs b/Libraries/LibWeb/CSS/Rust/src/animation.rs new file mode 100644 index 0000000000000..95b3f1e1850d5 --- /dev/null +++ b/Libraries/LibWeb/CSS/Rust/src/animation.rs @@ -0,0 +1,7139 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +//! CSS animation value interpolation. + +// Animation values use the same thread-confined shared graph as the rest of the style core. +#![allow(clippy::arc_with_non_send_sync)] + +use std::sync::Arc; + +use crate::property_metadata::{property_animation_type, property_numeric_ranges}; +use crate::style_value::{ + ColorBase, GridTrackEntryKind, RetainedGridTrackEntry, RetainedGridTrackEntryList, RetainedNumericRangeList, + RetainedShapePoint, RetainedShapePointList, RetainedStyleValueData, RetainedStyleValueDataList, + RetainedUtf16FlyString, RetainedUtf16FlyStringList, StyleValueData, +}; + +pub(crate) const ANIMATION_TYPE_DISCRETE: u8 = 0; +pub(crate) const ANIMATION_TYPE_BY_COMPUTED_VALUE: u8 = 1; +const ANIMATION_TYPE_REPEATABLE_LIST: u8 = 2; +const ANIMATION_TYPE_CUSTOM: u8 = 3; +pub(crate) const ANIMATION_TYPE_NONE: u8 = 4; +const VALUE_TYPE_ANGLE: u8 = 2; +const VALUE_TYPE_FLEX: u8 = 15; +const VALUE_TYPE_FREQUENCY: u8 = 21; +const VALUE_TYPE_INTEGER: u8 = 24; +const VALUE_TYPE_LENGTH: u8 = 25; +const VALUE_TYPE_NUMBER: u8 = 27; +const VALUE_TYPE_PERCENTAGE: u8 = 31; +const VALUE_TYPE_RATIO: u8 = 33; +const VALUE_TYPE_RESOLUTION: u8 = 35; +const VALUE_TYPE_TIME: u8 = 38; +const TRANSFORM_FUNCTION_MATRIX: u8 = 0; +const TRANSFORM_FUNCTION_MATRIX_3D: u8 = 1; +const TRANSFORM_FUNCTION_PERSPECTIVE: u8 = 2; +const TRANSFORM_FUNCTION_TRANSLATE: u8 = 3; +const TRANSFORM_FUNCTION_TRANSLATE_3D: u8 = 4; +const TRANSFORM_FUNCTION_TRANSLATE_X: u8 = 5; +const TRANSFORM_FUNCTION_TRANSLATE_Y: u8 = 6; +const TRANSFORM_FUNCTION_TRANSLATE_Z: u8 = 7; +const TRANSFORM_FUNCTION_SCALE: u8 = 8; +const TRANSFORM_FUNCTION_SCALE_3D: u8 = 9; +const TRANSFORM_FUNCTION_SCALE_X: u8 = 10; +const TRANSFORM_FUNCTION_SCALE_Y: u8 = 11; +const TRANSFORM_FUNCTION_SCALE_Z: u8 = 12; +const TRANSFORM_FUNCTION_ROTATE: u8 = 13; +const TRANSFORM_FUNCTION_ROTATE_3D: u8 = 14; +const TRANSFORM_FUNCTION_ROTATE_X: u8 = 15; +const TRANSFORM_FUNCTION_ROTATE_Y: u8 = 16; +const TRANSFORM_FUNCTION_ROTATE_Z: u8 = 17; +const TRANSFORM_FUNCTION_SKEW: u8 = 18; +const TRANSFORM_FUNCTION_SKEW_X: u8 = 19; +const TRANSFORM_FUNCTION_SKEW_Y: u8 = 20; +const OPEN_TYPE_MODE_FONT_VARIATION_SETTINGS: u8 = 1; +const FONT_STYLE_NORMAL: u8 = 0; +const FONT_STYLE_OBLIQUE: u8 = 4; +const STEP_POSITION_JUMP_START: u8 = 0; +const STEP_POSITION_JUMP_NONE: u8 = 2; +const STEP_POSITION_JUMP_BOTH: u8 = 3; +const STEP_POSITION_START: u8 = 4; +const GRID_REPEAT_FIXED: u8 = 2; +const BASIC_SHAPE_INSET: u8 = 0; +const BASIC_SHAPE_CIRCLE: u8 = 3; +const BASIC_SHAPE_ELLIPSE: u8 = 4; +const BASIC_SHAPE_POLYGON: u8 = 5; +const COLOR_TYPE_RGB: u8 = 0; +const COLOR_TYPE_A98_RGB: u8 = 1; +const COLOR_TYPE_DISPLAY_P3: u8 = 2; +const COLOR_TYPE_DISPLAY_P3_LINEAR: u8 = 3; +const COLOR_TYPE_HSL: u8 = 4; +const COLOR_TYPE_HWB: u8 = 5; +const COLOR_TYPE_LAB: u8 = 6; +const COLOR_TYPE_LCH: u8 = 7; +const COLOR_TYPE_OKLAB: u8 = 8; +const COLOR_TYPE_OKLCH: u8 = 9; +const COLOR_TYPE_SRGB: u8 = 10; +const COLOR_TYPE_SRGB_LINEAR: u8 = 11; +const COLOR_TYPE_PROPHOTO_RGB: u8 = 12; +const COLOR_TYPE_REC2020: u8 = 13; +const COLOR_TYPE_XYZ_D50: u8 = 14; +const COLOR_TYPE_XYZ_D65: u8 = 15; +const COLOR_SYNTAX_LEGACY: u8 = 0; +const COLOR_SYNTAX_MODERN: u8 = 1; + +#[derive(Clone, Copy)] +struct NumericRangeOverride { + value_type: u8, + min: f64, + max: f64, +} + +const BORDER_RADIUS_RECT_RANGES: &[NumericRangeOverride] = &[ + NumericRangeOverride { + value_type: VALUE_TYPE_LENGTH, + min: 0.0, + max: f32::MAX as f64, + }, + NumericRangeOverride { + value_type: VALUE_TYPE_PERCENTAGE, + min: 0.0, + max: f32::MAX as f64, + }, +]; + +const RADIAL_SIZE_RANGES: &[NumericRangeOverride] = &[NumericRangeOverride { + value_type: VALUE_TYPE_LENGTH, + min: 0.0, + max: f32::MAX as f64, +}]; + +const NONNEGATIVE_LENGTH_RANGE: &[NumericRangeOverride] = &[NumericRangeOverride { + value_type: VALUE_TYPE_LENGTH, + min: 0.0, + max: f32::MAX as f64, +}]; + +#[repr(C)] +pub struct FfiAnimationValueResult { + pub value: *const StyleValueData, + pub handled: bool, +} + +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum FfiEasingKind { + Linear, + CubicBezier, + Steps, +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct FfiLinearEasingPoint { + pub input: f64, + pub output: f64, +} + +#[repr(C)] +pub struct FfiEasingDescriptor { + pub kind: FfiEasingKind, + pub linear_points: *const FfiLinearEasingPoint, + pub linear_point_count: usize, + pub x1: f64, + pub y1: f64, + pub x2: f64, + pub y2: f64, + pub interval_count: i32, + pub step_position: u8, +} + +fn evaluate_linear_easing(points: &[FfiLinearEasingPoint], input_progress: f64, before_flag: bool) -> f64 { + // https://drafts.csswg.org/css-easing/#linear-easing-function-output + // To calculate linear easing output progress for a given linear easing function func, + // an input progress value inputProgress, and an optional before flag (defaulting to false), + // perform the following: + + // 1. Let points be func’s control points. + + // 2. If points holds only a single item, return the output progress value of that item. + if points.len() == 1 { + return points[0].output; + } + + // 3. If inputProgress matches the input progress value of the first point in points, + // and the before flag is true, return the first point’s output progress value. + if input_progress == points[0].input && before_flag { + return points[0].output; + } + + // 4. If inputProgress matches the input progress value of at least one point in points, + // return the output progress value of the last such point. + if let Some(point) = points.iter().rfind(|point| input_progress == point.input) { + return point.output; + } + + // 5. Otherwise, find two control points in points, A and B, which will be used for interpolation: + let (a, b) = if input_progress < points[0].input { + // 1. If inputProgress is smaller than any input progress value in points, + // let A and B be the first two items in points. + // If A and B have the same input progress value, return A’s output progress value. + let (a, b) = (&points[0], &points[1]); + if a.input == b.input { + return a.output; + } + (a, b) + } else if input_progress > points[points.len() - 1].input { + // 2. If inputProgress is larger than any input progress value in points, + // let A and B be the last two items in points. + // If A and B have the same input progress value, return B’s output progress value. + let (a, b) = (&points[points.len() - 2], &points[points.len() - 1]); + if a.input == b.input { + return b.output; + } + (a, b) + } else { + // 3. Otherwise, let A be the last control point whose input progress value is smaller than inputProgress, + // and let B be the first control point whose input progress value is larger than inputProgress. + let a = points + .iter() + .rfind(|point| point.input < input_progress) + .expect("canonical linear easing has a preceding point"); + let b = points + .iter() + .find(|point| point.input > input_progress) + .expect("canonical linear easing has a following point"); + (a, b) + }; + + // 6. Linearly interpolate (or extrapolate) inputProgress along the line defined by A and B, and return the result. + let factor = (input_progress - a.input) / (b.input - a.input); + a.output + factor * (b.output - a.output) +} + +fn cubic_bezier_at(first: f64, second: f64, parameter: f64) -> f64 { + let a = 1.0 - 3.0 * second + 3.0 * first; + let b = 3.0 * second - 6.0 * first; + let c = 3.0 * first; + (a * parameter * parameter * parameter) + (b * parameter * parameter) + (c * parameter) +} + +fn evaluate_cubic_bezier_easing(x1: f64, y1: f64, x2: f64, y2: f64, input_progress: f64) -> f64 { + // https://drafts.csswg.org/css-easing-1/#cubic-bezier-algo + // For input progress values outside the range [0, 1], the curve is extended infinitely using tangent of the curve + // at the closest endpoint as follows: + + // - For input progress values less than zero, + if input_progress < 0.0 { + // 1. If the x value of P1 is greater than zero, use a straight line that passes through P1 and P0 as the + // tangent. + if x1 > 0.0 { + return y1 / x1 * input_progress; + } + + // 2. Otherwise, if the x value of P2 is greater than zero, use a straight line that passes through P2 and P0 as + // the tangent. + if x2 > 0.0 { + return y2 / x2 * input_progress; + } + + // 3. Otherwise, let the output progress value be zero for all input progress values in the range [-∞, 0). + return 0.0; + } + + // - For input progress values greater than one, + if input_progress > 1.0 { + // 1. If the x value of P2 is less than one, use a straight line that passes through P2 and P3 as the tangent. + if x2 < 1.0 { + return (1.0 - y2) / (1.0 - x2) * (input_progress - 1.0) + 1.0; + } + + // 2. Otherwise, if the x value of P1 is less than one, use a straight line that passes through P1 and P3 as the + // tangent. + if x1 < 1.0 { + return (1.0 - y1) / (1.0 - x1) * (input_progress - 1.0) + 1.0; + } + + // 3. Otherwise, let the output progress value be one for all input progress values in the range (1, ∞]. + return 1.0; + } + + // The evaluation of this curve is covered in many sources such as [FUND-COMP-GRAPHICS]. + // NB: Use Newton-Raphson iteration to solve x(t) = inputProgress, then fall back to bisection. + let derivative = |parameter: f64| { + let a = 1.0 - 3.0 * x2 + 3.0 * x1; + let b = 3.0 * x2 - 6.0 * x1; + let c = 3.0 * x1; + 3.0 * a * parameter * parameter + 2.0 * b * parameter + c + }; + let epsilon = 1e-7; + let mut parameter = input_progress; + for _ in 0..8 { + let difference = cubic_bezier_at(x1, x2, parameter) - input_progress; + if difference.abs() < epsilon { + return cubic_bezier_at(y1, y2, parameter); + } + let derivative = derivative(parameter); + if derivative.abs() < 1e-12 { + break; + } + parameter -= difference / derivative; + } + + let mut low = 0.0; + let mut high = 1.0; + parameter = input_progress; + for _ in 0..64 { + let value = cubic_bezier_at(x1, x2, parameter); + if (value - input_progress).abs() < epsilon { + return cubic_bezier_at(y1, y2, parameter); + } + if input_progress > value { + low = parameter; + } else { + high = parameter; + } + parameter = (low + high) / 2.0; + } + cubic_bezier_at(y1, y2, parameter) +} + +fn evaluate_steps_easing(interval_count: i32, position: u8, input_progress: f64, before_flag: bool) -> f64 { + // https://drafts.csswg.org/css-easing-1/#step-easing-algo + let mut current_step = (input_progress * f64::from(interval_count)).floor(); + + // 2. If the step position property is one of: + // - jump-start, + // - jump-both, + // increment current step by one. + if matches!( + position, + STEP_POSITION_JUMP_START | STEP_POSITION_START | STEP_POSITION_JUMP_BOTH + ) { + current_step += 1.0; + } + + // 3. If both of the following conditions are true: + // - the before flag is set, and + // - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then + // decrement current step by one. + let step_progress = input_progress * f64::from(interval_count); + if before_flag && step_progress.trunc() == step_progress { + current_step -= 1.0; + } + + // 4. If input progress value ≥ 0 and current step < 0, let current step be zero. + if input_progress >= 0.0 && current_step < 0.0 { + current_step = 0.0; + } + + // 5. Calculate jumps based on the step position as follows: + + // jump-start or jump-end -> steps + // jump-none -> steps - 1 + // jump-both -> steps + 1 + let jumps = match position { + STEP_POSITION_JUMP_NONE => interval_count - 1, + STEP_POSITION_JUMP_BOTH => interval_count + 1, + _ => interval_count, + }; + + // 6. If input progress value ≤ 1 and current step > jumps, let current step be jumps. + if input_progress <= 1.0 && current_step > f64::from(jumps) { + current_step = f64::from(jumps); + } + + // 7. The output progress value is current step / jumps. + current_step / f64::from(jumps) +} + +/// Evaluate a resolved easing descriptor without consulting C++. +/// +/// # Safety +/// `descriptor` must point to a live descriptor. Its linear point range must be live when the +/// descriptor kind is linear. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_evaluate_easing( + descriptor: *const FfiEasingDescriptor, + input_progress: f64, + before_flag: bool, +) -> f64 { + crate::abort_on_panic(|| evaluate_easing_descriptor(unsafe { &*descriptor }, input_progress, before_flag)) +} + +fn evaluate_easing_descriptor(descriptor: &FfiEasingDescriptor, input_progress: f64, before_flag: bool) -> f64 { + match descriptor.kind { + FfiEasingKind::Linear => { + let points = unsafe { std::slice::from_raw_parts(descriptor.linear_points, descriptor.linear_point_count) }; + assert!(!points.is_empty()); + evaluate_linear_easing(points, input_progress, before_flag) + } + FfiEasingKind::CubicBezier => evaluate_cubic_bezier_easing( + descriptor.x1, + descriptor.y1, + descriptor.x2, + descriptor.y2, + input_progress, + ), + FfiEasingKind::Steps => evaluate_steps_easing( + descriptor.interval_count, + descriptor.step_position, + input_progress, + before_flag, + ), + } +} + +#[repr(C)] +pub struct FfiAnimationFontMetrics { + pub font_size: f64, + pub x_height: f64, + pub cap_height: f64, + pub zero_advance: f64, + pub line_height: f64, +} + +#[repr(C)] +pub struct FfiAnimationLengthResolutionContext { + pub viewport_width: f64, + pub viewport_height: f64, + pub font_metrics: FfiAnimationFontMetrics, + pub root_font_metrics: FfiAnimationFontMetrics, + pub font_metrics_depend_on_viewport_metrics: bool, + pub root_font_metrics_depend_on_viewport_metrics: bool, +} + +#[repr(C)] +pub struct FfiAnimationContext { + pub allow_discrete: bool, + pub current_color: *const StyleValueData, + pub has_length_resolution_context: bool, + pub length_resolution_context: FfiAnimationLengthResolutionContext, + pub has_transform_reference_box: bool, + pub transform_reference_box_width: f64, + pub transform_reference_box_height: f64, +} + +#[repr(C)] +pub struct FfiAnimationKeyframeValue { + pub key: i64, + pub value: *const StyleValueData, + pub easing: FfiEasingDescriptor, + pub composite: FfiCompositeOperation, +} + +#[repr(C)] +pub struct FfiAnimationValueInput { + pub property_id: u16, + pub underlying: *const StyleValueData, + pub initial: *const StyleValueData, + pub current_key: f64, + pub keyframes: *const FfiAnimationKeyframeValue, + pub keyframe_count: usize, +} + +#[repr(C)] +pub struct FfiAnimationBatch { + pub declarations: *const FfiAnimationDeclaration, + pub declaration_count: usize, + pub writing_mode: u8, + pub direction: u8, + pub important_property_bitmap: *const u8, + pub important_property_bitmap_length: usize, +} + +#[repr(C)] +pub struct FfiComputedAnimationBatch { + pub context: FfiAnimationContext, + pub values: *const FfiAnimationValueInput, + pub value_count: usize, + pub results: *mut FfiAnimatedProperty, + pub result_capacity: usize, +} + +#[repr(C)] +pub struct FfiAnimatedProperty { + pub property_id: u16, + pub value: *const StyleValueData, + pub progress: f32, + pub start_index: usize, + pub end_index: usize, + pub handled: bool, + pub apply: bool, +} + +#[repr(C)] +pub struct FfiAnimationCallbacks { + pub context: *mut std::ffi::c_void, + pub compute_values: unsafe extern "C" fn( + context: *mut std::ffi::c_void, + properties: *const FfiResolvedAnimationProperty, + property_count: usize, + ) -> FfiComputedAnimationBatch, +} + +#[repr(C)] +pub struct FfiAnimationDeclaration { + pub keyframe_index: usize, + pub property_id: u16, + pub value: *const StyleValueData, + pub use_initial: bool, + pub is_transition: bool, +} + +struct AnimationPropertyConflictCandidate { + keyframe_index: usize, + physical_property_id: u16, + source_property_id: u16, + source_longhand_id: u16, + value: RetainedStyleValueData, + use_initial: bool, + suppressed_by_important: bool, +} + +fn property_is_important(property_id: u16, bitmap: &[u8]) -> bool { + let Some(index) = property_id + .checked_sub(crate::property_metadata::FIRST_LONGHAND_PROPERTY_ID) + .map(usize::from) + else { + return false; + }; + bitmap.get(index / 8).is_some_and(|byte| byte & (1 << (index % 8)) != 0) +} + +fn animation_property_is_suppressed(is_transition: bool, property_id: u16, important_bitmap: &[u8]) -> bool { + !is_transition && property_is_important(property_id, important_bitmap) +} + +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FfiAnimationSpecifiedValueSource { + Value, + Inherited, + Initial, + Underlying, +} + +fn animation_specified_value_source(value: &StyleValueData, property_id: u16) -> FfiAnimationSpecifiedValueSource { + let StyleValueData::Keyword { keyword } = value else { + return FfiAnimationSpecifiedValueSource::Value; + }; + + // https://www.w3.org/TR/css-cascade-4/#inherit + // If the cascaded value of a property is the inherit keyword, the property's specified and + // computed values are the inherited value. + if *keyword == crate::style_compute::keyword::INHERIT { + return FfiAnimationSpecifiedValueSource::Inherited; + } + + // https://www.w3.org/TR/css-cascade-4/#inherit-initial + // If the cascaded value of a property is the unset keyword, then if it is an inherited + // property, this is treated as inherit, and if it is not, this is treated as initial. + if *keyword == crate::style_compute::keyword::UNSET { + return if crate::property_metadata::property_is_inherited(property_id) { + FfiAnimationSpecifiedValueSource::Inherited + } else { + FfiAnimationSpecifiedValueSource::Initial + }; + } + + // https://www.w3.org/TR/css-cascade-4/#initial + // If the cascaded value of a property is the initial keyword, the property's specified value + // is its initial value. + if *keyword == crate::style_compute::keyword::INITIAL { + return FfiAnimationSpecifiedValueSource::Initial; + } + if matches!( + *keyword, + crate::style_compute::keyword::REVERT | crate::style_compute::keyword::REVERT_LAYER + ) { + return FfiAnimationSpecifiedValueSource::Underlying; + } + FfiAnimationSpecifiedValueSource::Value +} + +fn resolve_animation_property_conflicts( + candidates: &[AnimationPropertyConflictCandidate], + selected: &mut [bool], + value_sources: &mut [FfiAnimationSpecifiedValueSource], +) { + assert_eq!(candidates.len(), selected.len()); + assert_eq!(candidates.len(), value_sources.len()); + selected.fill(false); + for (candidate, source) in candidates.iter().zip(value_sources.iter_mut()) { + *source = animation_specified_value_source(candidate.value.data(), candidate.physical_property_id); + } + let mut winners = std::collections::HashMap::<(usize, u16), usize>::new(); + for (candidate_index, candidate) in candidates.iter().enumerate() { + let key = (candidate.keyframe_index, candidate.physical_property_id); + let Some(&winner_index) = winners.get(&key) else { + winners.insert(key, candidate_index); + selected[candidate_index] = true; + continue; + }; + let winner = &candidates[winner_index]; + if candidate.use_initial { + continue; + } + if !winner.use_initial + && !crate::property_metadata::animation_property_is_preferred( + candidate.source_property_id, + winner.source_property_id, + ) + { + continue; + } + selected[winner_index] = false; + selected[candidate_index] = true; + winners.insert(key, candidate_index); + } +} + +#[repr(C)] +pub struct FfiResolvedAnimationProperty { + pub keyframe_index: usize, + pub physical_property_id: u16, + pub source_longhand_id: u16, + pub value: *const StyleValueData, + pub value_source: FfiAnimationSpecifiedValueSource, +} + +fn resolve_animation_declarations( + declarations: &[FfiAnimationDeclaration], + writing_mode: u8, + direction: u8, + important_property_bitmap: &[u8], +) -> ResolvedAnimationDeclarations { + let mut candidates = Vec::new(); + for declaration in declarations { + assert!( + !declaration.value.is_null(), + "animation declaration value must not be null" + ); + crate::style_compute::expand_shorthands_with( + declaration.property_id, + declaration.value.cast(), + false, + &mut |longhand_id, data, _| { + let physical_property_id = + crate::style_compute::map_logical_alias_to_physical(longhand_id, writing_mode, direction); + candidates.push(AnimationPropertyConflictCandidate { + keyframe_index: declaration.keyframe_index, + physical_property_id, + source_property_id: declaration.property_id, + source_longhand_id: longhand_id, + value: unsafe { + RetainedStyleValueData::from_retained_pointer(crate::style_value::rust_style_value_retain( + data.cast(), + )) + }, + use_initial: declaration.use_initial, + // OPTIMIZATION: Values resulting from animations other than CSS transitions + // are overridden by important properties, so there is no need to compute or + // evaluate them. + suppressed_by_important: animation_property_is_suppressed( + declaration.is_transition, + physical_property_id, + important_property_bitmap, + ), + }); + }, + ); + } + + let mut selected = vec![false; candidates.len()]; + let mut value_sources = vec![FfiAnimationSpecifiedValueSource::Value; candidates.len()]; + resolve_animation_property_conflicts(&candidates, &mut selected, &mut value_sources); + let mut properties = Vec::new(); + let mut retained_values = Vec::new(); + for ((candidate, selected), value_source) in candidates.into_iter().zip(selected).zip(value_sources) { + if selected && !candidate.suppressed_by_important { + properties.push(FfiResolvedAnimationProperty { + keyframe_index: candidate.keyframe_index, + physical_property_id: candidate.physical_property_id, + source_longhand_id: candidate.source_longhand_id, + value: candidate.value.pointer(), + value_source, + }); + retained_values.push(candidate.value); + } + } + ResolvedAnimationDeclarations { + properties, + _retained_values: retained_values, + } +} + +struct ResolvedAnimationDeclarations { + properties: Vec, + _retained_values: Vec, +} + +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum FfiCompositeOperation { + Replace, + Add, + Accumulate, +} + +fn accepted_range(property_id: u16, value_type: u8, range_overrides: &[NumericRangeOverride]) -> Option<(f64, f64)> { + if let Some(range) = range_overrides.iter().find(|range| range.value_type == value_type) { + return Some((range.min, range.max)); + } + property_numeric_ranges(property_id) + .iter() + .find(|range| range.value_type == value_type) + .map(|range| (range.min, range.max)) +} + +fn clamp_to_range(value: f64, range: Option<(f64, f64)>) -> f64 { + let Some((min, max)) = range else { + return value; + }; + if value < min { + min + } else if value > max { + max + } else { + value + } +} + +fn interpolate_f64(from: f64, to: f64, delta: f32, range: Option<(f64, f64)>) -> f64 { + clamp_to_range(from + (to - from) * f64::from(delta), range) +} + +fn interpolate_i32(from: i32, to: i32, delta: f32, range: Option<(f64, f64)>) -> i32 { + // https://drafts.csswg.org/css-values/#combine-integers + // Interpolation of is defined as Vresult = round((1 - p) × VA + p × VB); + // that is, interpolation happens in the real number space as for s, and the result is converted to an by rounding to the nearest integer. + let value = (from as f32 + (to as f32 - from as f32) * delta).round(); + clamp_to_range(f64::from(value), range) as i32 +} + +// https://drafts.csswg.org/css-borders-4/#normalized-superellipse-half-corner +fn normalized_super_ellipse_half_corner(s: f64) -> f64 { + // To compute the normalized superellipse half corner given a superellipse parameter s, return the first matching statement, switching on s: + + // -∞ Return 0. + if s == f64::NEG_INFINITY { + return 0.0; + } + + // ∞ Return 1. + if s == f64::INFINITY { + return 1.0; + } + + // Otherwise + // 1. Let k be 0.5^abs(s). + let k = 0.5_f64.powf(s.abs()); + + // 2. Let convexHalfCorner be 0.5^k. + let convex_half_corner = 0.5_f64.powf(k); + + // 3. If s is less than 0, return 1 - convexHalfCorner. + if s < 0.0 { + return 1.0 - convex_half_corner; + } + + // 4. Return convexHalfCorner. + convex_half_corner +} + +fn interpolation_value_to_super_ellipse_parameter(interpolation_value: f64) -> f64 { + // To convert a interpolationValue back to a superellipse parameter, switch on interpolationValue: + + // 0 Return -∞. + if interpolation_value == 0.0 { + return f64::NEG_INFINITY; + } + + // 0.5 Return 0. + if interpolation_value == 0.5 { + return 0.0; + } + + // 1 Return ∞. + if interpolation_value == 1.0 { + return f64::INFINITY; + } + + // Otherwise + // 1. Let convexHalfCorner be interpolationValue. + let mut convex_half_corner = interpolation_value; + + // 2. If interpolationValue is less than 0.5, set convexHalfCorner to 1 - interpolationValue. + if interpolation_value < 0.5 { + convex_half_corner = 1.0 - interpolation_value; + } + + // 3. Let k be ln(0.5) / ln(convexHalfCorner). + let k = 0.5_f64.ln() / convex_half_corner.ln(); + + // 4. Let s be log2(k). + let mut s = k.log2(); + + // AD-HOC: The logs above can introduce slight inaccuracies, this can interfere with the behaviour of + // serializing superellipse style values as their equivalent keywords as that relies on exact + // equality. To mitigate this we simply round to a whole number if we are sufficiently near + if (s.round() - s).abs() < f64::from(f32::EPSILON) { + s = s.round(); + } + + // 5. If interpolationValue is less than 0.5, return -s. + if interpolation_value < 0.5 { + return -s; + } + + // 6. Return s. + s +} + +fn angle_to_degrees(value: f64, unit: u8) -> Option { + let ratio = match unit { + 0 => 1.0, + 1 => 0.9, + 2 => 57.295_779_513_082_32, + 3 => 360.0, + _ => return None, + }; + Some(value * ratio) +} + +fn owned(value: StyleValueData) -> FfiAnimationValueResult { + FfiAnimationValueResult { + value: Arc::into_raw(Arc::new(value)), + handled: true, + } +} + +fn not_handled() -> FfiAnimationValueResult { + FfiAnimationValueResult { + value: std::ptr::null(), + handled: false, + } +} + +fn handled_without_value() -> FfiAnimationValueResult { + FfiAnimationValueResult { + value: std::ptr::null(), + handled: true, + } +} + +fn handled_retained_value(value: RetainedStyleValueData) -> FfiAnimationValueResult { + let pointer = unsafe { crate::style_value::rust_style_value_retain(value.data()) }; + FfiAnimationValueResult { + value: pointer, + handled: true, + } +} + +fn discrete_value( + context: Option<&FfiAnimationContext>, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + if !context.is_some_and(|context| context.allow_discrete) { + return handled_without_value(); + } + let value = if delta < 0.5 { from } else { to }; + FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(value) }, + handled: true, + } +} + +fn interpolate_visibility( + context: Option<&FfiAnimationContext>, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + let (StyleValueData::Keyword { keyword: from_keyword }, StyleValueData::Keyword { keyword: to_keyword }) = + (from, to) + else { + return not_handled(); + }; + + if from_keyword == to_keyword { + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(from) }, + handled: true, + }; + } + + // https://drafts.csswg.org/web-animations-1/#animating-visibility + // For the visibility property, visible is interpolated as a discrete step where values of p between 0 and 1 map to visible and other values of p map to the closer endpoint. + // If neither value is visible, then discrete animation is used. + let visible = crate::style_compute::keyword::VISIBLE; + if *from_keyword == visible || *to_keyword == visible { + let value = if delta <= 0.0 { + from + } else if delta >= 1.0 { + to + } else if *from_keyword == visible { + from + } else { + to + }; + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(value) }, + handled: true, + }; + } + + discrete_value(context, from, to, delta) +} + +fn interpolate_content_visibility( + context: Option<&FfiAnimationContext>, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + let (StyleValueData::Keyword { keyword: from_keyword }, StyleValueData::Keyword { keyword: to_keyword }) = + (from, to) + else { + return not_handled(); + }; + + if from_keyword == to_keyword { + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(from) }, + handled: true, + }; + } + + // https://drafts.csswg.org/css-contain/#content-visibility-animation + // In general, the content-visibility property’s animation type is discrete. + // However, similar to interpolation of visibility, during interpolation between hidden and any other content-visibility value, + // p values between 0 and 1 map to the non-hidden value. + let hidden = crate::style_compute::keyword::HIDDEN; + if *from_keyword == hidden || *to_keyword == hidden { + if !context.is_some_and(|context| context.allow_discrete) { + return handled_without_value(); + } + let value = if delta <= 0.0 { + from + } else if delta >= 1.0 || *from_keyword == hidden { + to + } else { + from + }; + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(value) }, + handled: true, + }; + } + + discrete_value(context, from, to, delta) +} + +fn interpolate_display( + context: Option<&FfiAnimationContext>, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + let (StyleValueData::Display { raw: from_raw }, StyleValueData::Display { raw: to_raw }) = (from, to) else { + return not_handled(); + }; + + if from_raw == to_raw { + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(from) }, + handled: true, + }; + } + + // https://drafts.csswg.org/css-display-4/#display-animation + // In general, the display property’s animation type is discrete. However, similar to interpolation of + // visibility (see Web Animations §  Animation of visibility), during interpolation between none and any + // other display value, p values between 0 and 1 map to the non-none value. Additionally, the element is + // inert as long as its display value would compute to none when ignoring the Transitions and Animations + // cascade origins. + // FIXME: Implement the inertness portion of this. + let from_is_none = crate::style_compute::display_is_none(*from_raw); + let to_is_none = crate::style_compute::display_is_none(*to_raw); + if from_is_none || to_is_none { + if !context.is_some_and(|context| context.allow_discrete) { + return handled_without_value(); + } + let value = if delta <= 0.0 { + from + } else if delta >= 1.0 || from_is_none { + to + } else { + from + }; + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(value) }, + handled: true, + }; + } + + discrete_value(context, from, to, delta) +} + +fn interpolate_scale(from: &StyleValueData, to: &StyleValueData, delta: f32) -> FfiAnimationValueResult { + let none = crate::style_compute::none_keyword(); + if matches!(from, StyleValueData::Keyword { keyword } if *keyword == none) + && matches!(to, StyleValueData::Keyword { keyword } if *keyword == none) + { + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(from) }, + handled: true, + }; + } + + // https://drafts.csswg.org/css-transforms-2/#propdef-scale + // Animation type: by computed value, but see below for none + // The scale property accepts 1-3 values, each specifying a scale along one axis, in order X, Y, then Z. + // If the Y value is not given, then it defaults to being the same as the X value. + // If the Z value is not given, then it defaults to 1. + // A is equivalent to a , for example scale: 100% is equivalent to scale: 1. Numbers are used + // during serialization of specified and computed values. + // When translate, rotate or scale are animating or transitioning, and the from value or to value (but not both) is + // none, the value none is replaced by the equivalent identity value (0px for translate, 0deg for rotate, 1 for scale). + let decode = |value: &StyleValueData| { + if matches!(value, StyleValueData::Keyword { keyword } if *keyword == none) { + return Some(vec![1.0, 1.0]); + } + let StyleValueData::Transformation { values, .. } = value else { + return None; + }; + if !matches!(values.as_slice().len(), 2 | 3) { + return None; + } + values + .as_slice() + .iter() + .map(|value| match value.data() { + StyleValueData::Number { value } => Some(*value), + StyleValueData::Percentage { value } => Some(*value / 100.0), + calculated @ StyleValueData::Calculated { .. } => { + crate::calc::resolve_calculated_number_without_context(calculated).or_else(|| { + crate::calc::resolve_calculated_percentage_without_context(calculated) + .map(|value| value / 100.0) + }) + } + _ => None, + }) + .collect::>>() + }; + let (Some(mut from), Some(mut to)) = (decode(from), decode(to)) else { + return not_handled(); + }; + let is_3d = from.len() == 3 || to.len() == 3; + if is_3d { + from.resize(3, 1.0); + to.resize(3, 1.0); + } + let values = from + .into_iter() + .zip(to) + .map(|(from, to)| retained_number(interpolate_f64(from, to, delta, None))) + .collect(); + owned(StyleValueData::Transformation { + property: crate::property_metadata::property_id::SCALE, + transform_function: if is_3d { + TRANSFORM_FUNCTION_SCALE_3D + } else { + TRANSFORM_FUNCTION_SCALE + }, + values: RetainedStyleValueDataList::from_retained_values(values), + }) +} + +fn length_percentage_calculation_node(value: &StyleValueData) -> Option> { + match value { + StyleValueData::Length { value, unit } => Some(Arc::new(crate::calc::CalcNode::Numeric( + crate::calc::CalcNumericValue::Length { + value: *value, + unit: *unit, + }, + ))), + StyleValueData::Percentage { value } => Some(Arc::new(crate::calc::CalcNode::Numeric( + crate::calc::CalcNumericValue::Percentage(*value), + ))), + StyleValueData::Calculated { rust_calculation, .. } => Some(rust_calculation.node_arc()), + _ => None, + } +} + +fn numeric_calculation_node(value: &StyleValueData) -> Option> { + let numeric = match value { + StyleValueData::Number { value } => crate::calc::CalcNumericValue::Number { + value: *value, + number_type: 0, + }, + StyleValueData::Integer { value } => crate::calc::CalcNumericValue::Number { + value: *value as f64, + number_type: 2, + }, + StyleValueData::Angle { value, unit } => crate::calc::CalcNumericValue::Angle { + value: *value, + unit: *unit, + }, + StyleValueData::Flex { value, unit } => crate::calc::CalcNumericValue::Flex { + value: *value, + unit: *unit, + }, + StyleValueData::Frequency { value, unit } => crate::calc::CalcNumericValue::Frequency { + value: *value, + unit: *unit, + }, + StyleValueData::Length { value, unit } => crate::calc::CalcNumericValue::Length { + value: *value, + unit: *unit, + }, + StyleValueData::Percentage { value } => crate::calc::CalcNumericValue::Percentage(*value), + StyleValueData::Resolution { value, unit } => crate::calc::CalcNumericValue::Resolution { + value: *value, + unit: *unit, + }, + StyleValueData::Time { value, unit } => crate::calc::CalcNumericValue::Time { + value: *value, + unit: *unit, + }, + StyleValueData::Calculated { rust_calculation, .. } => return Some(rust_calculation.node_arc()), + _ => return None, + }; + Some(Arc::new(crate::calc::CalcNode::Numeric(numeric))) +} + +struct AnimationCalculationContext<'a> { + resolve_as_is_number: bool, + resolve_as_base: u8, + has_percentages_resolve_as: bool, + percentages_resolve_as: u8, + resolve_numbers_as_integers: bool, + accepted_ranges: &'a RetainedNumericRangeList, +} + +fn animation_calculation_context(value: &StyleValueData) -> Option> { + let StyleValueData::Calculated { + resolve_as_is_number, + resolve_as_base, + has_percentages_resolve_as, + percentages_resolve_as, + resolve_numbers_as_integers, + accepted_ranges, + .. + } = value + else { + return None; + }; + Some(AnimationCalculationContext { + resolve_as_is_number: *resolve_as_is_number, + resolve_as_base: *resolve_as_base, + has_percentages_resolve_as: *has_percentages_resolve_as, + percentages_resolve_as: *percentages_resolve_as, + resolve_numbers_as_integers: *resolve_numbers_as_integers, + accepted_ranges, + }) +} + +fn retained_calculation( + calculation: Arc, + resolved_type: crate::calc::FfiNumericType, + context: &AnimationCalculationContext<'_>, +) -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Calculated { + rust_calculation: crate::calc::CalcNodeHandle::from_arc(calculation), + resolve_as_is_number: context.resolve_as_is_number, + resolve_as_base: context.resolve_as_base, + resolved_type, + has_percentages_resolve_as: context.has_percentages_resolve_as, + percentages_resolve_as: context.percentages_resolve_as, + resolve_numbers_as_integers: context.resolve_numbers_as_integers, + accepted_ranges: context.accepted_ranges.clone_owned(), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn retained_length_percentage_calculation( + calculation: Arc, + resolved_type: crate::calc::FfiNumericType, +) -> RetainedStyleValueData { + let value = match &*calculation { + crate::calc::CalcNode::Numeric(crate::calc::CalcNumericValue::Length { value, unit }) => { + StyleValueData::Length { + value: *value, + unit: *unit, + } + } + crate::calc::CalcNode::Numeric(crate::calc::CalcNumericValue::Percentage(value)) => { + StyleValueData::Percentage { value: *value } + } + _ => StyleValueData::Calculated { + rust_calculation: crate::calc::CalcNodeHandle::from_arc(calculation), + resolve_as_is_number: false, + resolve_as_base: 0, + resolved_type, + has_percentages_resolve_as: true, + percentages_resolve_as: VALUE_TYPE_LENGTH, + resolve_numbers_as_integers: false, + accepted_ranges: RetainedNumericRangeList::empty(), + }, + }; + let value = Arc::into_raw(Arc::new(value)); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn interpolate_translate_component( + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> Option { + let direct = interpolate_scalar_value(property_id, from, to, delta, &[]); + if direct.handled && !direct.value.is_null() { + return Some(unsafe { RetainedStyleValueData::from_retained_pointer(direct.value) }); + } + + // https://drafts.csswg.org/css-values-4/#combine-mixed + // The computed value of a percentage-dimension mix is defined as + // a computed percentage if the dimension component is zero + let dimension_component_is_zero = match (from, to) { + (StyleValueData::Length { value, .. }, StyleValueData::Percentage { .. }) => { + *value * (1.0 - delta as f64) == 0.0 + } + (StyleValueData::Percentage { .. }, StyleValueData::Length { value, .. }) => *value * delta as f64 == 0.0, + _ => false, + }; + if dimension_component_is_zero { + let percentage = match (from, to) { + (StyleValueData::Length { .. }, StyleValueData::Percentage { value }) => *value * delta as f64, + (StyleValueData::Percentage { value }, StyleValueData::Length { .. }) => *value * (1.0 - delta as f64), + _ => unreachable!(), + }; + let value = Arc::into_raw(Arc::new(StyleValueData::Percentage { value: percentage })); + return Some(unsafe { RetainedStyleValueData::from_retained_pointer(value) }); + } + + let from = length_percentage_calculation_node(from)?; + let to = length_percentage_calculation_node(to)?; + let (calculation, resolved_type) = crate::calc::interpolate_length_percentage_calculations(from, to, delta)?; + Some(retained_length_percentage_calculation(calculation, resolved_type)) +} + +fn interpolate_translate(from: &StyleValueData, to: &StyleValueData, delta: f32) -> FfiAnimationValueResult { + let none = crate::style_compute::none_keyword(); + if matches!(from, StyleValueData::Keyword { keyword } if *keyword == none) + && matches!(to, StyleValueData::Keyword { keyword } if *keyword == none) + { + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(from) }, + handled: true, + }; + } + + // https://drafts.csswg.org/css-transforms-2/#propdef-translate + // Animation type: by computed value, but see below for none + // The translate property accepts 1-3 values, each specifying a translation against one axis, in the order X, Y, + // then Z. When the second or third values are missing, they default to 0px. + // If the third value is omitted or zero, this specifies a 2d translation, equivalent to the translate() function. + // Otherwise, this specifies a 3d translation, equivalent to the translate3d() function. + // When translate, rotate or scale are animating or transitioning, and the from value or to value (but not both) is + // none, the value none is replaced by the equivalent identity value (0px for translate, 0deg for rotate, 1 for scale). + let decode = |value: &StyleValueData| { + let zero = || { + let zero = Arc::into_raw(Arc::new(StyleValueData::Length { + value: 0.0, + unit: crate::calc::canonical_pixel_unit(), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(zero) } + }; + if matches!(value, StyleValueData::Keyword { keyword } if *keyword == none) { + return Some(vec![zero(), zero()]); + } + let StyleValueData::Transformation { values, .. } = value else { + return None; + }; + if !matches!(values.as_slice().len(), 2 | 3) { + return None; + } + Some( + values + .as_slice() + .iter() + .map(|value| value.clone_retained()) + .collect::>(), + ) + }; + let (Some(mut from), Some(mut to)) = (decode(from), decode(to)) else { + return not_handled(); + }; + let is_3d = from.len() == 3 || to.len() == 3; + let zero = || { + let zero = Arc::into_raw(Arc::new(StyleValueData::Length { + value: 0.0, + unit: crate::calc::canonical_pixel_unit(), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(zero) } + }; + if is_3d { + from.resize_with(3, zero); + to.resize_with(3, zero); + } + let values = from + .iter() + .zip(to.iter()) + .map(|(from, to)| { + interpolate_translate_component( + crate::property_metadata::property_id::TRANSLATE, + from.data(), + to.data(), + delta, + ) + }) + .collect::>>(); + let Some(values) = values else { + return not_handled(); + }; + owned(StyleValueData::Transformation { + property: crate::property_metadata::property_id::TRANSLATE, + transform_function: if is_3d { + TRANSFORM_FUNCTION_TRANSLATE_3D + } else { + TRANSFORM_FUNCTION_TRANSLATE + }, + values: RetainedStyleValueDataList::from_retained_values(values), + }) +} + +fn interpolate_individual_rotate(from: &StyleValueData, to: &StyleValueData, delta: f32) -> FfiAnimationValueResult { + let none = crate::style_compute::none_keyword(); + if matches!(from, StyleValueData::Keyword { keyword } if *keyword == none) + && matches!(to, StyleValueData::Keyword { keyword } if *keyword == none) + { + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(from) }, + handled: true, + }; + } + + // https://drafts.csswg.org/css-transforms-2/#propdef-rotate + // Animation type: as SLERP, but see below for none + // The rotate property accepts an angle to rotate an element, and optionally an axis to rotate it around. + // When translate, rotate or scale are animating or transitioning, and the from value or to value (but not both) is + // none, the value none is replaced by the equivalent identity value (0px for translate, 0deg for rotate, 1 for scale). + let is_none = |value: &StyleValueData| matches!(value, StyleValueData::Keyword { keyword } if *keyword == none); + let is_2d = |value: &StyleValueData| { + is_none(value) + || matches!(value, StyleValueData::Transformation { transform_function, values, .. } + if *transform_function == TRANSFORM_FUNCTION_ROTATE && values.as_slice().len() == 1) + }; + if is_2d(from) && is_2d(to) { + let angle = |value: &StyleValueData| { + if is_none(value) { + return Some(0.0); + } + let StyleValueData::Transformation { values, .. } = value else { + return None; + }; + let [angle] = values.as_slice() else { + return None; + }; + match angle.data() { + StyleValueData::Angle { value, unit } => angle_to_degrees(*value, *unit), + _ => None, + } + }; + let (Some(from), Some(to)) = (angle(from), angle(to)) else { + return not_handled(); + }; + let angle = Arc::into_raw(Arc::new(StyleValueData::Angle { + value: interpolate_f64(from, to, delta, None), + unit: 0, + })); + return owned(StyleValueData::Transformation { + property: crate::property_metadata::property_id::ROTATE, + transform_function: TRANSFORM_FUNCTION_ROTATE, + values: RetainedStyleValueDataList::from_retained_values(vec![unsafe { + RetainedStyleValueData::from_retained_pointer(angle) + }]), + }); + } + + let normalize = |value: &StyleValueData| { + if is_none(value) { + let angle = Arc::into_raw(Arc::new(StyleValueData::Angle { value: 0.0, unit: 0 })); + return Some(RetainedStyleValueDataList::from_retained_values(vec![ + retained_number(0.0), + retained_number(0.0), + retained_number(1.0), + unsafe { RetainedStyleValueData::from_retained_pointer(angle) }, + ])); + } + let StyleValueData::Transformation { + transform_function, + values, + .. + } = value + else { + return None; + }; + match (*transform_function, values.as_slice()) { + (TRANSFORM_FUNCTION_ROTATE, [angle]) => Some(RetainedStyleValueDataList::from_retained_values(vec![ + retained_number(0.0), + retained_number(0.0), + retained_number(1.0), + angle.clone_retained(), + ])), + (TRANSFORM_FUNCTION_ROTATE_3D, [..]) if values.as_slice().len() == 4 => { + Some(RetainedStyleValueDataList::from_retained_values( + values + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + )) + } + _ => None, + } + }; + let (Some(from), Some(to)) = (normalize(from), normalize(to)) else { + return not_handled(); + }; + interpolate_rotate_3d( + crate::property_metadata::property_id::ROTATE, + TRANSFORM_FUNCTION_ROTATE_3D, + &from, + &to, + delta, + ) + .map_or_else(not_handled, owned) +} + +fn interpolate_font_variation_settings( + context: Option<&FfiAnimationContext>, + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + // https://drafts.csswg.org/css-fonts/#font-variation-settings-def + // Two declarations of font-feature-settings can be animated between if they are "like". "Like" declarations + // are ones where the same set of properties appear (in any order). Because successive duplicate properties + // are applied instead of prior duplicate properties, two declarations can be "like" even if they have + // differing number of properties. If two declarations are "like" then animation occurs pairwise between + // corresponding values in the declarations. Otherwise, animation is not possible. + if !matches!(from, StyleValueData::ValueList { .. }) || !matches!(to, StyleValueData::ValueList { .. }) { + return discrete_value(context, from, to, delta); + } + + // NB: The values in these lists have already been deduplicated and sorted at this point, so we can + // interpolate them pairwise. + let result = interpolate_scalar_value(property_id, from, to, delta, &[]); + if !result.handled || result.value.is_null() { + return discrete_value(context, from, to, delta); + } + result +} + +fn radius_components_equal(first: &StyleValueData, second: &StyleValueData) -> bool { + match (first, second) { + ( + StyleValueData::Length { + value: first_value, + unit: first_unit, + }, + StyleValueData::Length { + value: second_value, + unit: second_unit, + }, + ) => first_value == second_value && first_unit == second_unit, + (StyleValueData::Percentage { value: first }, StyleValueData::Percentage { value: second }) => first == second, + _ => false, + } +} + +struct ExpandedGridTrack<'a> { + track: &'a RetainedGridTrackEntry, + line_names: Option<&'a RetainedUtf16FlyStringList>, +} + +fn empty_retained_style_value() -> RetainedStyleValueData { + unsafe { RetainedStyleValueData::from_retained_optional_pointer(std::ptr::null()) } +} + +fn empty_grid_line_names() -> RetainedUtf16FlyStringList { + RetainedUtf16FlyStringList::from_retained_strings(Vec::new()) +} + +fn grid_nested_entries(entry: &RetainedGridTrackEntry) -> &[RetainedGridTrackEntry] { + if entry.repeat_entries_pointer.is_null() { + return &[]; + } + unsafe { std::slice::from_raw_parts(entry.repeat_entries_pointer, entry.repeat_entries_length) } +} + +fn grid_entries_into_raw_parts(entries: Vec) -> (*mut RetainedGridTrackEntry, usize) { + let entries = entries.into_boxed_slice(); + let length = entries.len(); + (Box::into_raw(entries) as *mut RetainedGridTrackEntry, length) +} + +fn clone_grid_track_entry(entry: &RetainedGridTrackEntry) -> RetainedGridTrackEntry { + let (repeat_entries_pointer, repeat_entries_length) = + grid_entries_into_raw_parts(grid_nested_entries(entry).iter().map(clone_grid_track_entry).collect()); + RetainedGridTrackEntry { + kind: entry.kind, + names: entry.names.clone_retained(), + size_value: entry.size_value.clone_retained(), + min_value: entry.min_value.clone_retained(), + max_value: entry.max_value.clone_retained(), + repeat_type: entry.repeat_type, + repeat_count: entry.repeat_count.clone_retained(), + repeat_is_subgrid: entry.repeat_is_subgrid, + repeat_preserve_line_name_sets: entry.repeat_preserve_line_name_sets, + repeat_entries_pointer, + repeat_entries_length, + } +} + +fn grid_line_names_entry(names: &RetainedUtf16FlyStringList) -> RetainedGridTrackEntry { + RetainedGridTrackEntry { + kind: GridTrackEntryKind::LineNames, + names: names.clone_retained(), + size_value: empty_retained_style_value(), + min_value: empty_retained_style_value(), + max_value: empty_retained_style_value(), + repeat_type: 0, + repeat_count: empty_retained_style_value(), + repeat_is_subgrid: false, + repeat_preserve_line_name_sets: false, + repeat_entries_pointer: std::ptr::null_mut(), + repeat_entries_length: 0, + } +} + +fn expand_grid_tracks_and_lines(entries: &[RetainedGridTrackEntry]) -> Option>> { + let mut result = Vec::new(); + let mut current_track = None; + let mut current_line_names = None; + + for entry in entries { + if entry.kind == GridTrackEntryKind::LineNames { + if current_line_names.is_some() { + return None; + } + current_line_names = Some(&entry.names); + } else { + if let Some(track) = current_track.take() { + result.push(ExpandedGridTrack { + track, + line_names: current_line_names.take(), + }); + } + current_track = Some(entry); + } + + if current_track.is_some() && current_line_names.is_some() { + result.push(ExpandedGridTrack { + track: current_track.take().expect("checked above"), + line_names: current_line_names.take(), + }); + } + } + if let Some(track) = current_track { + result.push(ExpandedGridTrack { + track, + line_names: current_line_names, + }); + } + Some(result) +} + +fn append_grid_track_with_line_names( + result: &mut Vec, + track: RetainedGridTrackEntry, + line_names: Option<&RetainedUtf16FlyStringList>, +) { + result.push(track); + if let Some(line_names) = line_names { + result.push(grid_line_names_entry(line_names)); + } +} + +fn interpolate_grid_component( + property_id: u16, + from: &RetainedStyleValueData, + to: &RetainedStyleValueData, + delta: f32, +) -> RetainedStyleValueData { + if let Some(value) = interpolate_translate_component(property_id, from.data(), to.data(), delta) { + return value; + } + if delta < 0.5 { + from.clone_retained() + } else { + to.clone_retained() + } +} + +fn interpolate_grid_track_entries( + property_id: u16, + from_is_subgrid: bool, + from_entries: &[RetainedGridTrackEntry], + to_is_subgrid: bool, + to_entries: &[RetainedGridTrackEntry], + delta: f32, +) -> Option> { + // https://drafts.csswg.org/css-grid-2/#track-sizing + // Animation type: if the list lengths match, by computed value type per item in the computed track list; + // discrete otherwise. + // + // https://drafts.csswg.org/css-grid-2/#computed-track-list-subgrid + // The computed track list of a subgrid axis is the subgrid keyword followed by a list of line names. + if from_is_subgrid || to_is_subgrid { + return None; + } + + let expanded_from = expand_grid_tracks_and_lines(from_entries)?; + let expanded_to = expand_grid_tracks_and_lines(to_entries)?; + if expanded_from.len() != expanded_to.len() { + return None; + } + + let mut result = Vec::new(); + for (from, to) in expanded_from.iter().zip(&expanded_to) { + let line_names = if delta < 0.5 { from.line_names } else { to.line_names }; + let track = match (from.track.kind, to.track.kind) { + (GridTrackEntryKind::Repeat, GridTrackEntryKind::Repeat) => { + // https://drafts.csswg.org/css-grid/#repeat-interpolation + if from.track.repeat_type != GRID_REPEAT_FIXED || to.track.repeat_type != GRID_REPEAT_FIXED { + return None; + } + let (StyleValueData::Integer { value: from_count }, StyleValueData::Integer { value: to_count }) = + (from.track.repeat_count.data(), to.track.repeat_count.data()) + else { + return None; + }; + if from_count != to_count { + return None; + } + let nested = interpolate_grid_track_entries( + property_id, + from.track.repeat_is_subgrid, + grid_nested_entries(from.track), + to.track.repeat_is_subgrid, + grid_nested_entries(to.track), + delta, + )?; + let (repeat_entries_pointer, repeat_entries_length) = grid_entries_into_raw_parts(nested); + RetainedGridTrackEntry { + kind: GridTrackEntryKind::Repeat, + names: empty_grid_line_names(), + size_value: empty_retained_style_value(), + min_value: empty_retained_style_value(), + max_value: empty_retained_style_value(), + repeat_type: from.track.repeat_type, + repeat_count: from.track.repeat_count.clone_retained(), + repeat_is_subgrid: false, + repeat_preserve_line_name_sets: false, + repeat_entries_pointer, + repeat_entries_length, + } + } + (GridTrackEntryKind::Repeat, _) | (_, GridTrackEntryKind::Repeat) => return None, + (GridTrackEntryKind::MinMax, GridTrackEntryKind::MinMax) => RetainedGridTrackEntry { + kind: GridTrackEntryKind::MinMax, + names: empty_grid_line_names(), + size_value: empty_retained_style_value(), + min_value: interpolate_grid_component(property_id, &from.track.min_value, &to.track.min_value, delta), + max_value: interpolate_grid_component(property_id, &from.track.max_value, &to.track.max_value, delta), + repeat_type: 0, + repeat_count: empty_retained_style_value(), + repeat_is_subgrid: false, + repeat_preserve_line_name_sets: false, + repeat_entries_pointer: std::ptr::null_mut(), + repeat_entries_length: 0, + }, + (GridTrackEntryKind::Size, GridTrackEntryKind::Size) => RetainedGridTrackEntry { + kind: GridTrackEntryKind::Size, + names: empty_grid_line_names(), + size_value: interpolate_grid_component( + property_id, + &from.track.size_value, + &to.track.size_value, + delta, + ), + min_value: empty_retained_style_value(), + max_value: empty_retained_style_value(), + repeat_type: 0, + repeat_count: empty_retained_style_value(), + repeat_is_subgrid: false, + repeat_preserve_line_name_sets: false, + repeat_entries_pointer: std::ptr::null_mut(), + repeat_entries_length: 0, + }, + _ => { + if delta < 0.5 { + clone_grid_track_entry(from.track) + } else { + clone_grid_track_entry(to.track) + } + } + }; + append_grid_track_with_line_names(&mut result, track, line_names); + } + Some(result) +} + +fn composite_grid_component( + underlying: &RetainedStyleValueData, + animated: &RetainedStyleValueData, + operation: FfiCompositeOperation, +) -> RetainedStyleValueData { + let result = composite_scalar_value(underlying.data(), animated.data(), operation); + if result.handled && !result.value.is_null() { + return unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }; + } + + if let (Some(underlying), Some(animated)) = ( + length_percentage_calculation_node(underlying.data()), + length_percentage_calculation_node(animated.data()), + ) && let Some((calculation, resolved_type)) = + crate::calc::add_length_percentage_calculations(underlying, animated) + { + // https://drafts.csswg.org/css-values-4/#combine-mixed + // Addition of is defined the same as interpolation except by adding each component rather than interpolating it. + return retained_length_percentage_calculation(calculation, resolved_type); + } + + animated.clone_retained() +} + +fn composite_grid_track_entries( + underlying_is_subgrid: bool, + underlying_entries: &[RetainedGridTrackEntry], + animated_is_subgrid: bool, + animated_entries: &[RetainedGridTrackEntry], + operation: FfiCompositeOperation, +) -> Option> { + // https://drafts.csswg.org/css-grid-2/#track-sizing + // Animation type: if the list lengths match, by computed value type per item in the computed track list; + // discrete otherwise. + // + // https://drafts.csswg.org/css-grid-2/#computed-track-list-subgrid + // The computed track list of a subgrid axis is the subgrid keyword followed by a list of line names. + if underlying_is_subgrid || animated_is_subgrid { + return None; + } + + let expanded_underlying = expand_grid_tracks_and_lines(underlying_entries)?; + let expanded_animated = expand_grid_tracks_and_lines(animated_entries)?; + if expanded_underlying.len() != expanded_animated.len() { + return None; + } + + let mut result = Vec::new(); + for (underlying, animated) in expanded_underlying.iter().zip(&expanded_animated) { + let track = match (underlying.track.kind, animated.track.kind) { + (GridTrackEntryKind::Repeat, GridTrackEntryKind::Repeat) => { + if underlying.track.repeat_type != GRID_REPEAT_FIXED || animated.track.repeat_type != GRID_REPEAT_FIXED + { + return None; + } + let ( + StyleValueData::Integer { + value: underlying_count, + }, + StyleValueData::Integer { value: animated_count }, + ) = (underlying.track.repeat_count.data(), animated.track.repeat_count.data()) + else { + return None; + }; + if underlying_count != animated_count { + return None; + } + let nested = composite_grid_track_entries( + underlying.track.repeat_is_subgrid, + grid_nested_entries(underlying.track), + animated.track.repeat_is_subgrid, + grid_nested_entries(animated.track), + operation, + )?; + let (repeat_entries_pointer, repeat_entries_length) = grid_entries_into_raw_parts(nested); + RetainedGridTrackEntry { + kind: GridTrackEntryKind::Repeat, + names: empty_grid_line_names(), + size_value: empty_retained_style_value(), + min_value: empty_retained_style_value(), + max_value: empty_retained_style_value(), + repeat_type: underlying.track.repeat_type, + repeat_count: underlying.track.repeat_count.clone_retained(), + repeat_is_subgrid: false, + repeat_preserve_line_name_sets: false, + repeat_entries_pointer, + repeat_entries_length, + } + } + (GridTrackEntryKind::Repeat, _) | (_, GridTrackEntryKind::Repeat) => return None, + (GridTrackEntryKind::MinMax, GridTrackEntryKind::MinMax) => RetainedGridTrackEntry { + kind: GridTrackEntryKind::MinMax, + names: empty_grid_line_names(), + size_value: empty_retained_style_value(), + min_value: composite_grid_component(&underlying.track.min_value, &animated.track.min_value, operation), + max_value: composite_grid_component(&underlying.track.max_value, &animated.track.max_value, operation), + repeat_type: 0, + repeat_count: empty_retained_style_value(), + repeat_is_subgrid: false, + repeat_preserve_line_name_sets: false, + repeat_entries_pointer: std::ptr::null_mut(), + repeat_entries_length: 0, + }, + (GridTrackEntryKind::Size, GridTrackEntryKind::Size) => RetainedGridTrackEntry { + kind: GridTrackEntryKind::Size, + names: empty_grid_line_names(), + size_value: composite_grid_component( + &underlying.track.size_value, + &animated.track.size_value, + operation, + ), + min_value: empty_retained_style_value(), + max_value: empty_retained_style_value(), + repeat_type: 0, + repeat_count: empty_retained_style_value(), + repeat_is_subgrid: false, + repeat_preserve_line_name_sets: false, + repeat_entries_pointer: std::ptr::null_mut(), + repeat_entries_length: 0, + }, + _ => clone_grid_track_entry(animated.track), + }; + append_grid_track_with_line_names(&mut result, track, animated.line_names); + } + Some(result) +} + +fn retained_animation_result(result: FfiAnimationValueResult) -> Option { + if !result.handled || result.value.is_null() { + return None; + } + Some(unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }) +} + +fn retained_percentage(value: f64) -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Percentage { value })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn retained_computed_center_position() -> RetainedStyleValueData { + let edge = || { + let edge = Arc::into_raw(Arc::new(StyleValueData::Edge { + has_edge: false, + edge: 0, + offset: retained_percentage(50.0), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(edge) } + }; + let position = Arc::into_raw(Arc::new(StyleValueData::Position { + edge_x: edge(), + edge_y: edge(), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(position) } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ColorComponentCategory { + Red, + Green, + Blue, + Lightness, + OpponentA, + OpponentB, + Colorfulness, + Hue, + NotAnalogous, +} + +#[derive(Clone, Copy)] +struct NativeColor { + color_type: u8, + components: [f32; 4], + missing: [bool; 4], +} + +fn resolve_color_component(value: &StyleValueData, reference_value: f32) -> Option<(f32, bool)> { + match value { + StyleValueData::Number { value } => Some((*value as f32, false)), + StyleValueData::Percentage { value } => Some((*value as f32 / 100.0 * reference_value, false)), + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword() => Some((0.0, true)), + _ => None, + } +} + +fn native_color_components(value: &StyleValueData) -> Option { + let StyleValueData::ColorFunction { + color_base, + channel_0, + channel_1, + channel_2, + alpha, + origin_color, + .. + } = value + else { + return None; + }; + if !color_base.has_color_type || origin_color.optional_data().is_some() { + return None; + } + + let resolve_hue = |value: &StyleValueData| match value { + StyleValueData::Number { value } => Some((*value as f32, false)), + StyleValueData::Angle { value, unit } => angle_to_degrees(*value, *unit).map(|value| (value as f32, false)), + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword() => Some((0.0, true)), + _ => None, + }; + let resolve = |value: &RetainedStyleValueData, reference: f32| resolve_color_component(value.data(), reference); + let resolve_fraction = |value: &RetainedStyleValueData| { + let (value, missing) = resolve(value, 100.0)?; + Some((value / 100.0, missing)) + }; + let ((first, first_missing), (second, second_missing), (third, third_missing)) = match color_base.color_type { + COLOR_TYPE_RGB => ( + resolve(channel_0, 255.0)?, + resolve(channel_1, 255.0)?, + resolve(channel_2, 255.0)?, + ), + COLOR_TYPE_HSL | COLOR_TYPE_HWB => ( + resolve_hue(channel_0.data())?, + resolve_fraction(channel_1)?, + resolve_fraction(channel_2)?, + ), + COLOR_TYPE_LAB => ( + resolve(channel_0, 100.0)?, + resolve(channel_1, 125.0)?, + resolve(channel_2, 125.0)?, + ), + COLOR_TYPE_OKLAB => ( + resolve(channel_0, 1.0)?, + resolve(channel_1, 0.4)?, + resolve(channel_2, 0.4)?, + ), + COLOR_TYPE_LCH => ( + resolve(channel_0, 100.0)?, + resolve(channel_1, 150.0)?, + resolve_hue(channel_2.data())?, + ), + COLOR_TYPE_OKLCH => ( + resolve(channel_0, 1.0)?, + resolve(channel_1, 0.4)?, + resolve_hue(channel_2.data())?, + ), + _ => ( + resolve(channel_0, 1.0)?, + resolve(channel_1, 1.0)?, + resolve(channel_2, 1.0)?, + ), + }; + let (alpha, alpha_missing) = match alpha.optional_data() { + Some(value) => resolve_color_component(value, 1.0)?, + None => (1.0, false), + }; + let mut components = [first, second, third, alpha.clamp(0.0, 1.0)]; + if color_base.color_type == COLOR_TYPE_RGB { + components[0] = components[0].clamp(0.0, 255.0) / 255.0; + components[1] = components[1].clamp(0.0, 255.0) / 255.0; + components[2] = components[2].clamp(0.0, 255.0) / 255.0; + } + Some(NativeColor { + color_type: color_base.color_type, + components, + missing: [first_missing, second_missing, third_missing, alpha_missing], + }) +} + +fn color_component_categories(color_type: u8) -> [ColorComponentCategory; 3] { + use ColorComponentCategory::*; + match color_type { + COLOR_TYPE_HSL => [Hue, Colorfulness, Lightness], + COLOR_TYPE_HWB => [Hue, NotAnalogous, NotAnalogous], + COLOR_TYPE_LAB | COLOR_TYPE_OKLAB => [Lightness, OpponentA, OpponentB], + COLOR_TYPE_LCH | COLOR_TYPE_OKLCH => [Lightness, Colorfulness, Hue], + COLOR_TYPE_RGB + | COLOR_TYPE_A98_RGB + | COLOR_TYPE_DISPLAY_P3 + | COLOR_TYPE_DISPLAY_P3_LINEAR + | COLOR_TYPE_SRGB + | COLOR_TYPE_SRGB_LINEAR + | COLOR_TYPE_PROPHOTO_RGB + | COLOR_TYPE_REC2020 + | COLOR_TYPE_XYZ_D50 + | COLOR_TYPE_XYZ_D65 => [Red, Green, Blue], + _ => [NotAnalogous, NotAnalogous, NotAnalogous], + } +} + +// https://drafts.csswg.org/css-color-4/#interpolation-missing +// Carry forward missing components from the input color space to the interpolation color space. +// A missing component is carried forward if it has an analogous component in the target space. +// Additionally, if ALL components of an analogous set are missing, they are all carried forward. +fn carry_forward_missing_components( + source_missing: [bool; 4], + source_categories: [ColorComponentCategory; 3], + target_categories: [ColorComponentCategory; 3], +) -> [bool; 4] { + let mut result = [false; 4]; + + // Same-space: all components map to themselves, including NotAnalogous ones (e.g. HWB W/B). + if source_categories == target_categories { + result = source_missing; + return result; + } + + // Carry forward individual analogous components + for target_index in 0..3 { + if target_categories[target_index] == ColorComponentCategory::NotAnalogous { + continue; + } + for source_index in 0..3 { + if source_missing[source_index] && source_categories[source_index] == target_categories[target_index] { + result[target_index] = true; + break; + } + } + } + + // If every component of an analogous set is missing in the source, carry forward as a set. + // The analogous set consists of the components that remain after removing individually analogous ones. + let mut all_non_analogous_missing = true; + let mut has_non_analogous = false; + for source_index in 0..3 { + let is_individually_analogous = source_categories[source_index] != ColorComponentCategory::NotAnalogous + && target_categories.contains(&source_categories[source_index]); + if !is_individually_analogous { + has_non_analogous = true; + if !source_missing[source_index] { + all_non_analogous_missing = false; + } + } + } + if has_non_analogous && all_non_analogous_missing { + for target_index in 0..3 { + let is_individually_analogous = target_categories[target_index] != ColorComponentCategory::NotAnalogous + && source_categories.contains(&target_categories[target_index]); + if !is_individually_analogous { + result[target_index] = true; + } + } + } + + // Alpha is always analogous to itself. + result[3] = source_missing[3]; + result +} + +fn substitute_missing_components( + from_components: &mut [f32; 4], + to_components: &mut [f32; 4], + from_missing: [bool; 4], + to_missing: [bool; 4], +) { + for index in 0..3 { + if from_missing[index] && !to_missing[index] { + from_components[index] = to_components[index]; + } else if to_missing[index] && !from_missing[index] { + to_components[index] = from_components[index]; + } + } + + if from_missing[3] && !to_missing[3] { + from_components[3] = to_components[3]; + } else if to_missing[3] && !from_missing[3] { + to_components[3] = from_components[3]; + } else if from_missing[3] && to_missing[3] { + from_components[3] = 1.0; + to_components[3] = 1.0; + } +} + +fn legacy_srgb_components(value: &StyleValueData) -> Option<([f32; 4], [bool; 4])> { + let color = native_color_components(value)?; + let components = crate::color_conversion::legacy_color_to_srgb(color.color_type, color.components)?; + let missing = carry_forward_missing_components( + color.missing, + color_component_categories(color.color_type), + [ + ColorComponentCategory::Red, + ColorComponentCategory::Green, + ColorComponentCategory::Blue, + ], + ); + Some((components, missing)) +} + +fn interpolate_modern_color(from: &StyleValueData, to: &StyleValueData, delta: f32) -> Option { + // https://drafts.csswg.org/css-color-4/#interpolation + // 1. checking the two colors for analogous components and analogous sets which will be carried forward + let from = native_color_components(from)?; + let to = native_color_components(to)?; + + // 2. prepare both colors for conversion. this changes any powerless components to missing values + // NB: Every color handled here has native components, so step 2 does not mark additional components missing. + + // 3. converting them both to a given color space which will be referred to as the interpolation color space + // below. + let mut from_components = crate::color_conversion::to_oklab(from.color_type, from.components)?; + let mut to_components = crate::color_conversion::to_oklab(to.color_type, to.components)?; + let target_categories = color_component_categories(COLOR_TYPE_OKLAB); + let from_missing = carry_forward_missing_components( + from.missing, + color_component_categories(from.color_type), + target_categories, + ); + let to_missing = + carry_forward_missing_components(to.missing, color_component_categories(to.color_type), target_categories); + + // 4. (if required) re-inserting carried forward values in the converted colors + substitute_missing_components(&mut from_components, &mut to_components, from_missing, to_missing); + let interpolate = |from: f32, to: f32| from + (to - from) * delta; + let interpolated_alpha = interpolate(from_components[3], to_components[3]).clamp(0.0, 1.0); + + // https://drafts.csswg.org/css-color-4/#interpolation + let result = if interpolated_alpha == 0.0 { + // OPTIMIZATION: Fully transparent results can skip the premultiply/interpolate/unpremultiply cycle. + [0.0, 0.0, 0.0, 0.0] + } else { + // 6. changing the color components to premultiplied form + // https://drafts.csswg.org/css-color-4/#interpolation-alpha + // For rectangular orthogonal color coordinate systems, all component values are multiplied by the alpha value. + let from_premultiplied = [ + from_components[0] * from_components[3], + from_components[1] * from_components[3], + from_components[2] * from_components[3], + ]; + let to_premultiplied = [ + to_components[0] * to_components[3], + to_components[1] * to_components[3], + to_components[2] * to_components[3], + ]; + + // 7. linearly interpolating each component of the computed value of the color separately + let premultiplied = [ + interpolate(from_premultiplied[0], to_premultiplied[0]), + interpolate(from_premultiplied[1], to_premultiplied[1]), + interpolate(from_premultiplied[2], to_premultiplied[2]), + ]; + + // 8. undoing premultiplication + [ + premultiplied[0] / interpolated_alpha, + premultiplied[1] / interpolated_alpha, + premultiplied[2] / interpolated_alpha, + interpolated_alpha, + ] + }; + + // https://drafts.csswg.org/css-color-4/#interpolation-space + // If the host syntax does not define what color space interpolation should take place in, it defaults to Oklab. + let result_missing = [ + from_missing[0] && to_missing[0], + from_missing[1] && to_missing[1], + from_missing[2] && to_missing[2], + from_missing[3] && to_missing[3], + ]; + let number_or_none = |value: f32, is_missing: bool| { + if is_missing { + retained_none_keyword() + } else { + retained_number(value as f64) + } + }; + Some(StyleValueData::ColorFunction { + color_base: ColorBase { + has_color_type: true, + color_type: COLOR_TYPE_OKLAB, + color_syntax: COLOR_SYNTAX_MODERN, + }, + channel_0: number_or_none(result[0], result_missing[0]), + channel_1: number_or_none(result[1], result_missing[1]), + channel_2: number_or_none(result[2], result_missing[2]), + alpha: number_or_none(result[3], result_missing[3]), + has_name: false, + name: empty_retained_fly_string(), + origin_color: empty_retained_style_value(), + }) +} + +fn interpolate_legacy_rgb(from: &StyleValueData, to: &StyleValueData, delta: f32) -> Option { + // https://drafts.csswg.org/css-color-4/#interpolation-space + // If the host syntax does not define what color space interpolation should take place in, it defaults to Oklab. + // However, user agents must handle interpolation between legacy sRGB color formats (hex colors, named colors, + // rgb(), hsl() or hwb() and the equivalent alpha-including forms) in gamma-encoded sRGB space. + let (mut from, from_missing) = legacy_srgb_components(from)?; + let (mut to, to_missing) = legacy_srgb_components(to)?; + substitute_missing_components(&mut from, &mut to, from_missing, to_missing); + let interpolate = |from: f32, to: f32| from + (to - from) * delta; + let interpolated_alpha = interpolate(from[3], to[3]).clamp(0.0, 1.0); + + // https://drafts.csswg.org/css-color-4/#interpolation + let result = if interpolated_alpha == 0.0 { + // OPTIMIZATION: Fully transparent results can skip the premultiply/interpolate/unpremultiply cycle. + [0.0, 0.0, 0.0, 0.0] + } else { + // 6. changing the color components to premultiplied form + // https://drafts.csswg.org/css-color-4/#interpolation-alpha + // For rectangular orthogonal color coordinate systems, all component values are multiplied by the alpha value. + let from_premultiplied = [from[0] * from[3], from[1] * from[3], from[2] * from[3]]; + let to_premultiplied = [to[0] * to[3], to[1] * to[3], to[2] * to[3]]; + + // 7. linearly interpolating each component of the computed value of the color separately + let premultiplied = [ + interpolate(from_premultiplied[0], to_premultiplied[0]), + interpolate(from_premultiplied[1], to_premultiplied[1]), + interpolate(from_premultiplied[2], to_premultiplied[2]), + ]; + + // 8. undoing premultiplication + [ + premultiplied[0] / interpolated_alpha, + premultiplied[1] / interpolated_alpha, + premultiplied[2] / interpolated_alpha, + interpolated_alpha, + ] + }; + + // https://drafts.csswg.org/css-color-4/#interpolation-space + // NB: Legacy sRGB content interpolates in sRGB and produces a legacy rgb() result. + let to_byte = |value: f32| (value * 255.0).round().clamp(0.0, 255.0) as u8; + let alpha = to_byte(result[3]); + Some(StyleValueData::ColorFunction { + color_base: ColorBase { + has_color_type: true, + color_type: COLOR_TYPE_RGB, + color_syntax: COLOR_SYNTAX_LEGACY, + }, + channel_0: retained_number(to_byte(result[0]) as f64), + channel_1: retained_number(to_byte(result[1]) as f64), + channel_2: retained_number(to_byte(result[2]) as f64), + alpha: retained_number(alpha as f64 / 255.0), + has_name: false, + name: empty_retained_fly_string(), + origin_color: empty_retained_style_value(), + }) +} + +fn empty_shape_points() -> RetainedShapePointList { + RetainedShapePointList::from_retained_points(Vec::new()) +} + +fn empty_retained_fly_string() -> RetainedUtf16FlyString { + unsafe { RetainedUtf16FlyString::from_leaked_raw(0) } +} + +fn interpolate_basic_shape_component( + property_id: u16, + from: &RetainedStyleValueData, + to: &RetainedStyleValueData, + delta: f32, +) -> RetainedStyleValueData { + interpolate_translate_component(property_id, from.data(), to.data(), delta).unwrap_or_else(|| { + if delta < 0.5 { + from.clone_retained() + } else { + to.clone_retained() + } + }) +} + +struct RadialSizeComponents<'a> { + component_count: u8, + is_extent_0: bool, + value_0: &'a RetainedStyleValueData, + is_extent_1: bool, + value_1: &'a RetainedStyleValueData, +} + +fn interpolate_radial_size( + property_id: u16, + from: RadialSizeComponents<'_>, + to: RadialSizeComponents<'_>, + delta: f32, +) -> Option { + // https://drafts.csswg.org/css-images-4/#interpolating-gradients + // https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation + // FIXME: Radial extents should disallow interpolation for basic-shape values but should be converted into their + // equivalent length-percentage values for radial gradients + if from.is_extent_0 + || (from.component_count == 2 && from.is_extent_1) + || to.is_extent_0 + || (to.component_count == 2 && to.is_extent_1) + { + return None; + } + + let interpolate = |from: &RetainedStyleValueData, to: &RetainedStyleValueData| { + let direct = interpolate_scalar_value(property_id, from.data(), to.data(), delta, RADIAL_SIZE_RANGES); + if let Some(value) = retained_animation_result(direct) { + return Some(value); + } + interpolate_translate_component(property_id, from.data(), to.data(), delta) + }; + if from.component_count == 1 && to.component_count == 1 { + return Some(StyleValueData::RadialSize { + component_count: 1, + is_extent_0: false, + extent_0: 0, + value_0: interpolate(from.value_0, to.value_0)?, + is_extent_1: false, + extent_1: 0, + value_1: empty_retained_style_value(), + }); + } + + let from_vertical = if from.component_count == 2 { + from.value_1 + } else { + from.value_0 + }; + let to_vertical = if to.component_count == 2 { + to.value_1 + } else { + to.value_0 + }; + Some(StyleValueData::RadialSize { + component_count: 2, + is_extent_0: false, + extent_0: 0, + value_0: interpolate(from.value_0, to.value_0)?, + is_extent_1: false, + extent_1: 0, + value_1: interpolate(from_vertical, to_vertical)?, + }) +} + +fn interpolate_basic_shape( + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> Option { + let ( + StyleValueData::BasicShape { + kind: from_kind, + v0: from_v0, + v1: from_v1, + v2: from_v2, + v3: from_v3, + v4: from_v4, + fill_rule: from_fill_rule, + points: from_points, + .. + }, + StyleValueData::BasicShape { + kind: to_kind, + v0: to_v0, + v1: to_v1, + v2: to_v2, + v3: to_v3, + v4: to_v4, + fill_rule: to_fill_rule, + points: to_points, + .. + }, + ) = (from, to) + else { + return None; + }; + + // https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation + if from_kind != to_kind { + return None; + } + + let empty = empty_retained_style_value; + match *from_kind { + BASIC_SHAPE_INSET => { + // If both shapes are of type inset(), interpolate between each value in the shape functions. + Some(StyleValueData::BasicShape { + kind: *from_kind, + v0: interpolate_basic_shape_component(property_id, from_v0, to_v0, delta), + v1: interpolate_basic_shape_component(property_id, from_v1, to_v1, delta), + v2: interpolate_basic_shape_component(property_id, from_v2, to_v2, delta), + v3: interpolate_basic_shape_component(property_id, from_v3, to_v3, delta), + v4: interpolate_basic_shape_component(property_id, from_v4, to_v4, delta), + fill_rule: 0, + points: empty_shape_points(), + path_string: empty_retained_fly_string(), + }) + } + BASIC_SHAPE_CIRCLE | BASIC_SHAPE_ELLIPSE => { + // If both shapes are the same type, that type is ellipse() or circle(), and the radiuses are specified + // as (rather than keywords), interpolate between each value in the shape functions. + let radius = retained_animation_result(interpolate_scalar_value( + property_id, + from_v0.data(), + to_v0.data(), + delta, + &[], + ))?; + let position = match (from_v1.optional_data(), to_v1.optional_data()) { + (None, None) => empty(), + _ => { + let from_default; + let to_default; + let from = if from_v1.optional_data().is_some() { + from_v1 + } else { + from_default = retained_computed_center_position(); + &from_default + }; + let to = if to_v1.optional_data().is_some() { + to_v1 + } else { + to_default = retained_computed_center_position(); + &to_default + }; + retained_animation_result(interpolate_scalar_value( + property_id, + from.data(), + to.data(), + delta, + &[], + ))? + } + }; + Some(StyleValueData::BasicShape { + kind: *from_kind, + v0: radius, + v1: position, + v2: empty(), + v3: empty(), + v4: empty(), + fill_rule: 0, + points: empty_shape_points(), + path_string: empty_retained_fly_string(), + }) + } + BASIC_SHAPE_POLYGON => { + // If both shapes are of type polygon(), both polygons have the same number of vertices, and use the + // same <'fill-rule'>, interpolate between each value in the shape functions. + if from_fill_rule != to_fill_rule || from_points.as_slice().len() != to_points.as_slice().len() { + return None; + } + let points = from_points + .as_slice() + .iter() + .zip(to_points.as_slice()) + .map(|(from, to)| { + let [from_x, from_y] = from.values(); + let [to_x, to_y] = to.values(); + RetainedShapePoint::from_retained_values( + interpolate_basic_shape_component(property_id, from_x, to_x, delta), + interpolate_basic_shape_component(property_id, from_y, to_y, delta), + ) + }) + .collect(); + Some(StyleValueData::BasicShape { + kind: *from_kind, + v0: empty(), + v1: empty(), + v2: empty(), + v3: empty(), + v4: empty(), + fill_rule: *from_fill_rule, + points: RetainedShapePointList::from_retained_points(points), + path_string: empty_retained_fly_string(), + }) + } + _ => None, + } +} + +fn composite_retained_value( + underlying: &RetainedStyleValueData, + animated: &RetainedStyleValueData, + operation: FfiCompositeOperation, +) -> Option { + retained_animation_result(composite_scalar_value(underlying.data(), animated.data(), operation)) +} + +fn composite_radial_size( + underlying: RadialSizeComponents<'_>, + animated: RadialSizeComponents<'_>, + operation: FfiCompositeOperation, +) -> Option { + // https://drafts.csswg.org/css-images-4/#interpolating-gradients + // https://drafts.csswg.org/css-shapes-1/#basic-shape-interpolation + // FIXME: Radial extents should disallow composition for basic-shape values but should be converted into their + // equivalent length-percentage values for radial gradients + if underlying.is_extent_0 + || (underlying.component_count == 2 && underlying.is_extent_1) + || animated.is_extent_0 + || (animated.component_count == 2 && animated.is_extent_1) + { + return None; + } + + let composite = |underlying: &RetainedStyleValueData, animated: &RetainedStyleValueData| { + composite_retained_value(underlying, animated, operation) + }; + if underlying.component_count == 1 && animated.component_count == 1 { + return Some(StyleValueData::RadialSize { + component_count: 1, + is_extent_0: false, + extent_0: 0, + value_0: composite(underlying.value_0, animated.value_0)?, + is_extent_1: false, + extent_1: 0, + value_1: empty_retained_style_value(), + }); + } + + let underlying_vertical = if underlying.component_count == 2 { + underlying.value_1 + } else { + underlying.value_0 + }; + let animated_vertical = if animated.component_count == 2 { + animated.value_1 + } else { + animated.value_0 + }; + Some(StyleValueData::RadialSize { + component_count: 2, + is_extent_0: false, + extent_0: 0, + value_0: composite(underlying.value_0, animated.value_0)?, + is_extent_1: false, + extent_1: 0, + value_1: composite(underlying_vertical, animated_vertical)?, + }) +} + +fn composite_basic_shape( + underlying: &StyleValueData, + animated: &StyleValueData, + operation: FfiCompositeOperation, +) -> Option { + let ( + StyleValueData::BasicShape { + kind: underlying_kind, + v0: underlying_v0, + v1: underlying_v1, + v2: underlying_v2, + v3: underlying_v3, + v4: underlying_v4, + fill_rule: underlying_fill_rule, + points: underlying_points, + .. + }, + StyleValueData::BasicShape { + kind: animated_kind, + v0: animated_v0, + v1: animated_v1, + v2: animated_v2, + v3: animated_v3, + v4: animated_v4, + fill_rule: animated_fill_rule, + points: animated_points, + .. + }, + ) = (underlying, animated) + else { + return None; + }; + if underlying_kind != animated_kind { + return None; + } + + let empty = empty_retained_style_value; + match *underlying_kind { + BASIC_SHAPE_INSET => Some(StyleValueData::BasicShape { + kind: *underlying_kind, + v0: composite_retained_value(underlying_v0, animated_v0, operation)?, + v1: composite_retained_value(underlying_v1, animated_v1, operation)?, + v2: composite_retained_value(underlying_v2, animated_v2, operation)?, + v3: composite_retained_value(underlying_v3, animated_v3, operation)?, + v4: composite_retained_value(underlying_v4, animated_v4, operation)?, + fill_rule: 0, + points: empty_shape_points(), + path_string: empty_retained_fly_string(), + }), + BASIC_SHAPE_CIRCLE | BASIC_SHAPE_ELLIPSE => { + let position = match (underlying_v1.optional_data(), animated_v1.optional_data()) { + (None, None) => empty(), + _ => { + let underlying_default; + let animated_default; + let underlying = if underlying_v1.optional_data().is_some() { + underlying_v1 + } else { + underlying_default = retained_computed_center_position(); + &underlying_default + }; + let animated = if animated_v1.optional_data().is_some() { + animated_v1 + } else { + animated_default = retained_computed_center_position(); + &animated_default + }; + composite_retained_value(underlying, animated, operation)? + } + }; + Some(StyleValueData::BasicShape { + kind: *underlying_kind, + v0: composite_retained_value(underlying_v0, animated_v0, operation)?, + v1: position, + v2: empty(), + v3: empty(), + v4: empty(), + fill_rule: 0, + points: empty_shape_points(), + path_string: empty_retained_fly_string(), + }) + } + BASIC_SHAPE_POLYGON => { + if underlying_fill_rule != animated_fill_rule + || underlying_points.as_slice().len() != animated_points.as_slice().len() + { + return None; + } + let points = underlying_points + .as_slice() + .iter() + .zip(animated_points.as_slice()) + .map(|(underlying, animated)| { + let [underlying_x, underlying_y] = underlying.values(); + let [animated_x, animated_y] = animated.values(); + Some(RetainedShapePoint::from_retained_values( + composite_retained_value(underlying_x, animated_x, operation)?, + composite_retained_value(underlying_y, animated_y, operation)?, + )) + }) + .collect::>>()?; + Some(StyleValueData::BasicShape { + kind: *underlying_kind, + v0: empty(), + v1: empty(), + v2: empty(), + v3: empty(), + v4: empty(), + fill_rule: *underlying_fill_rule, + points: RetainedShapePointList::from_retained_points(points), + path_string: empty_retained_fly_string(), + }) + } + _ => None, + } +} + +fn composite_scalar_value( + underlying: &StyleValueData, + animated: &StyleValueData, + operation: FfiCompositeOperation, +) -> FfiAnimationValueResult { + if matches!(operation, FfiCompositeOperation::Replace) { + return handled_without_value(); + } + + if let Some(context) = animation_calculation_context(underlying).or_else(|| animation_calculation_context(animated)) + && let (Some(underlying), Some(animated)) = + (numeric_calculation_node(underlying), numeric_calculation_node(animated)) + && let Some((calculation, resolved_type)) = crate::calc::add_calculations( + underlying, + animated, + context.has_percentages_resolve_as, + context.resolve_as_is_number, + context.resolve_as_base, + ) + { + // https://drafts.csswg.org/css-values-4/#combine-math + // Addition of math functions, with each other or with numeric values and other numeric-valued functions, is defined as Vresult = calc(VA + VB). + return handled_retained_value(retained_calculation(calculation, resolved_type, &context)); + } + + if (matches!(underlying, StyleValueData::Calculated { .. }) + || matches!(animated, StyleValueData::Calculated { .. }) + || std::mem::discriminant(underlying) != std::mem::discriminant(animated)) + && let (Some(underlying), Some(animated)) = ( + length_percentage_calculation_node(underlying), + length_percentage_calculation_node(animated), + ) + && let Some((calculation, resolved_type)) = + crate::calc::add_length_percentage_calculations(underlying, animated) + { + // https://drafts.csswg.org/css-values-4/#combine-mixed + // Addition of is defined the same as interpolation except by adding each component rather than interpolating it. + return handled_retained_value(retained_length_percentage_calculation(calculation, resolved_type)); + } + + match (underlying, animated) { + (StyleValueData::ColorFunction { .. }, StyleValueData::ColorFunction { .. }) => { + // FIXME: Implement color addition and accumulation. + handled_without_value() + } + (StyleValueData::BasicShape { .. }, StyleValueData::BasicShape { .. }) => { + composite_basic_shape(underlying, animated, operation).map_or_else(handled_without_value, owned) + } + ( + StyleValueData::RadialSize { + component_count: underlying_component_count, + is_extent_0: underlying_is_extent_0, + value_0: underlying_value_0, + is_extent_1: underlying_is_extent_1, + value_1: underlying_value_1, + .. + }, + StyleValueData::RadialSize { + component_count: animated_component_count, + is_extent_0: animated_is_extent_0, + value_0: animated_value_0, + is_extent_1: animated_is_extent_1, + value_1: animated_value_1, + .. + }, + ) => composite_radial_size( + RadialSizeComponents { + component_count: *underlying_component_count, + is_extent_0: *underlying_is_extent_0, + value_0: underlying_value_0, + is_extent_1: *underlying_is_extent_1, + value_1: underlying_value_1, + }, + RadialSizeComponents { + component_count: *animated_component_count, + is_extent_0: *animated_is_extent_0, + value_0: animated_value_0, + is_extent_1: *animated_is_extent_1, + value_1: animated_value_1, + }, + operation, + ) + .map_or_else(handled_without_value, owned), + (StyleValueData::Number { value: underlying }, StyleValueData::Number { value: animated }) => { + // https://drafts.csswg.org/css-values-4/#combine-numbers + // Addition of is defined as Vresult = VA + VB. + owned(StyleValueData::Number { + value: underlying + animated, + }) + } + (StyleValueData::Integer { value: underlying }, StyleValueData::Integer { value: animated }) => { + // https://drafts.csswg.org/css-values-4/#combine-integers + // Addition of is defined as Vresult = VA + VB. + owned(StyleValueData::Integer { + value: underlying.saturating_add(*animated), + }) + } + ( + StyleValueData::Angle { + value: underlying, + unit: underlying_unit, + }, + StyleValueData::Angle { + value: animated, + unit: animated_unit, + }, + ) if underlying_unit == animated_unit => { + // https://drafts.csswg.org/css-values-4/#combine-dimensions + // Addition of compatible dimensions is defined as Vresult = VA + VB. + owned(StyleValueData::Angle { + value: underlying + animated, + unit: *underlying_unit, + }) + } + ( + StyleValueData::Flex { + value: underlying, + unit: underlying_unit, + }, + StyleValueData::Flex { + value: animated, + unit: animated_unit, + }, + ) if underlying_unit == animated_unit => owned(StyleValueData::Flex { + value: underlying + animated, + unit: *underlying_unit, + }), + ( + StyleValueData::Frequency { + value: underlying, + unit: underlying_unit, + }, + StyleValueData::Frequency { + value: animated, + unit: animated_unit, + }, + ) if underlying_unit == animated_unit => owned(StyleValueData::Frequency { + value: underlying + animated, + unit: *underlying_unit, + }), + ( + StyleValueData::Length { + value: underlying, + unit: underlying_unit, + }, + StyleValueData::Length { + value: animated, + unit: animated_unit, + }, + ) if underlying_unit == animated_unit => owned(StyleValueData::Length { + value: underlying + animated, + unit: *underlying_unit, + }), + (StyleValueData::Percentage { value: underlying }, StyleValueData::Percentage { value: animated }) => { + // https://drafts.csswg.org/css-values-4/#combine-mixed + // Addition of is defined the same as interpolation except by adding each component rather than interpolating it. + owned(StyleValueData::Percentage { + value: underlying + animated, + }) + } + ( + StyleValueData::Resolution { + value: underlying, + unit: underlying_unit, + }, + StyleValueData::Resolution { + value: animated, + unit: animated_unit, + }, + ) if underlying_unit == animated_unit => owned(StyleValueData::Resolution { + value: underlying + animated, + unit: *underlying_unit, + }), + ( + StyleValueData::Time { + value: underlying, + unit: underlying_unit, + }, + StyleValueData::Time { + value: animated, + unit: animated_unit, + }, + ) if underlying_unit == animated_unit => owned(StyleValueData::Time { + value: underlying + animated, + unit: *underlying_unit, + }), + (StyleValueData::OpacityValue { value: underlying }, StyleValueData::OpacityValue { value: animated }) => { + let (StyleValueData::Number { value: underlying }, StyleValueData::Number { value: animated }) = + (underlying.data(), animated.data()) + else { + return handled_without_value(); + }; + + // https://drafts.csswg.org/css-color-4/#propdef-opacity + // Computed value: specified number, clamped to the range [0,1] + let number = Arc::into_raw(Arc::new(StyleValueData::Number { + value: (underlying + animated).clamp(0.0, 1.0), + })); + owned(StyleValueData::OpacityValue { + value: unsafe { RetainedStyleValueData::from_retained_pointer(number) }, + }) + } + ( + StyleValueData::BackgroundSize { + size_x: underlying_x, + size_y: underlying_y, + }, + StyleValueData::BackgroundSize { + size_x: animated_x, + size_y: animated_y, + }, + ) => { + let x = composite_scalar_value(underlying_x.data(), animated_x.data(), operation); + let y = composite_scalar_value(underlying_y.data(), animated_y.data(), operation); + if !x.handled || !y.handled { + return handled_without_value(); + } + if x.value.is_null() || y.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::BackgroundSize { + size_x: unsafe { RetainedStyleValueData::from_retained_pointer(x.value) }, + size_y: unsafe { RetainedStyleValueData::from_retained_pointer(y.value) }, + }) + } + (StyleValueData::Edge { offset: underlying, .. }, StyleValueData::Edge { offset: animated, .. }) => { + let (Some(underlying), Some(animated)) = (underlying.optional_data(), animated.optional_data()) else { + return handled_without_value(); + }; + let result = composite_scalar_value(underlying, animated, operation); + if !result.handled { + return handled_without_value(); + } + if result.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::Edge { + has_edge: false, + edge: 0, + offset: unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }, + }) + } + ( + StyleValueData::Position { + edge_x: underlying_x, + edge_y: underlying_y, + }, + StyleValueData::Position { + edge_x: animated_x, + edge_y: animated_y, + }, + ) => { + let x = composite_scalar_value(underlying_x.data(), animated_x.data(), operation); + let y = composite_scalar_value(underlying_y.data(), animated_y.data(), operation); + if !x.handled || !y.handled { + return handled_without_value(); + } + if x.value.is_null() || y.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::Position { + edge_x: unsafe { RetainedStyleValueData::from_retained_pointer(x.value) }, + edge_y: unsafe { RetainedStyleValueData::from_retained_pointer(y.value) }, + }) + } + ( + StyleValueData::Rect { + top: underlying_top, + right: underlying_right, + bottom: underlying_bottom, + left: underlying_left, + }, + StyleValueData::Rect { + top: animated_top, + right: animated_right, + bottom: animated_bottom, + left: animated_left, + }, + ) => { + let combine = |underlying: &RetainedStyleValueData, animated: &RetainedStyleValueData| { + let result = composite_scalar_value(underlying.data(), animated.data(), operation); + if !result.handled { + return Err(()); + } + if result.value.is_null() { + return Ok(None); + } + Ok(Some(unsafe { + RetainedStyleValueData::from_retained_pointer(result.value) + })) + }; + let value = (|| { + let Some(top) = combine(underlying_top, animated_top)? else { + return Ok(None); + }; + let Some(right) = combine(underlying_right, animated_right)? else { + return Ok(None); + }; + let Some(bottom) = combine(underlying_bottom, animated_bottom)? else { + return Ok(None); + }; + let Some(left) = combine(underlying_left, animated_left)? else { + return Ok(None); + }; + Ok(Some(StyleValueData::Rect { + top, + right, + bottom, + left, + })) + })(); + match value { + Err(()) => handled_without_value(), + Ok(None) => handled_without_value(), + Ok(Some(value)) => owned(value), + } + } + ( + StyleValueData::BorderRadius { + horizontal_radius: underlying_horizontal, + vertical_radius: underlying_vertical, + .. + }, + StyleValueData::BorderRadius { + horizontal_radius: animated_horizontal, + vertical_radius: animated_vertical, + .. + }, + ) => { + let horizontal = + composite_scalar_value(underlying_horizontal.data(), animated_horizontal.data(), operation); + if !horizontal.handled { + return handled_without_value(); + } + if horizontal.value.is_null() { + return handled_without_value(); + } + let horizontal = unsafe { RetainedStyleValueData::from_retained_pointer(horizontal.value) }; + let vertical = composite_scalar_value(underlying_vertical.data(), animated_vertical.data(), operation); + if !vertical.handled { + return handled_without_value(); + } + if vertical.value.is_null() { + return handled_without_value(); + } + let vertical = unsafe { RetainedStyleValueData::from_retained_pointer(vertical.value) }; + owned(StyleValueData::BorderRadius { + is_elliptical: !radius_components_equal(horizontal.data(), vertical.data()), + horizontal_radius: horizontal, + vertical_radius: vertical, + }) + } + ( + StyleValueData::BorderRadiusRect { + top_left: underlying_top_left, + top_right: underlying_top_right, + bottom_right: underlying_bottom_right, + bottom_left: underlying_bottom_left, + }, + StyleValueData::BorderRadiusRect { + top_left: animated_top_left, + top_right: animated_top_right, + bottom_right: animated_bottom_right, + bottom_left: animated_bottom_left, + }, + ) => { + // https://drafts.csswg.org/web-animations-1/#animating-properties + // Corresponding individual components of the computed values are combined (interpolated, added, or accumulated) using the indicated procedure for that value type (see CSS Values 4 § 3 Combining Values: Interpolation, Addition, and Accumulation). + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + let combine = |underlying: &RetainedStyleValueData, animated: &RetainedStyleValueData| { + let result = composite_scalar_value(underlying.data(), animated.data(), operation); + if !result.handled { + return Err(()); + } + if result.value.is_null() { + return Ok(None); + } + Ok(Some(unsafe { + RetainedStyleValueData::from_retained_pointer(result.value) + })) + }; + let value = (|| { + let Some(top_left) = combine(underlying_top_left, animated_top_left)? else { + return Ok(None); + }; + let Some(top_right) = combine(underlying_top_right, animated_top_right)? else { + return Ok(None); + }; + let Some(bottom_right) = combine(underlying_bottom_right, animated_bottom_right)? else { + return Ok(None); + }; + let Some(bottom_left) = combine(underlying_bottom_left, animated_bottom_left)? else { + return Ok(None); + }; + Ok(Some(StyleValueData::BorderRadiusRect { + top_left, + top_right, + bottom_right, + bottom_left, + })) + })(); + match value { + Err(()) => handled_without_value(), + Ok(None) => handled_without_value(), + Ok(Some(value)) => owned(value), + } + } + ( + StyleValueData::BorderImageSlice { + top: underlying_top, + right: underlying_right, + bottom: underlying_bottom, + left: underlying_left, + fill: underlying_fill, + }, + StyleValueData::BorderImageSlice { + top: animated_top, + right: animated_right, + bottom: animated_bottom, + left: animated_left, + fill: animated_fill, + }, + ) => { + // https://drafts.csswg.org/web-animations-1/#animating-properties + // Corresponding individual components of the computed values are combined (interpolated, added, or accumulated) using the indicated procedure for that value type (see CSS Values 4 § 3 Combining Values: Interpolation, Addition, and Accumulation). + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + if underlying_fill != animated_fill { + return handled_without_value(); + } + let combine = |underlying: &RetainedStyleValueData, animated: &RetainedStyleValueData| { + let result = composite_scalar_value(underlying.data(), animated.data(), operation); + if !result.handled { + return Err(()); + } + if result.value.is_null() { + return Ok(None); + } + Ok(Some(unsafe { + RetainedStyleValueData::from_retained_pointer(result.value) + })) + }; + let value = (|| { + let Some(top) = combine(underlying_top, animated_top)? else { + return Ok(None); + }; + let Some(right) = combine(underlying_right, animated_right)? else { + return Ok(None); + }; + let Some(bottom) = combine(underlying_bottom, animated_bottom)? else { + return Ok(None); + }; + let Some(left) = combine(underlying_left, animated_left)? else { + return Ok(None); + }; + Ok(Some(StyleValueData::BorderImageSlice { + top, + right, + bottom, + left, + fill: *underlying_fill, + })) + })(); + match value { + Err(()) => handled_without_value(), + Ok(None) => handled_without_value(), + Ok(Some(value)) => owned(value), + } + } + ( + StyleValueData::OpenTypeTagged { + tag: underlying_tag, + packed_tag: underlying_packed_tag, + value: underlying_value, + .. + }, + StyleValueData::OpenTypeTagged { + tag: animated_tag, + value: animated_value, + .. + }, + ) => { + // https://drafts.csswg.org/web-animations-1/#animating-properties + // Corresponding individual components of the computed values are combined (interpolated, added, or accumulated) using the indicated procedure for that value type (see CSS Values 4 § 3 Combining Values: Interpolation, Addition, and Accumulation). + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + if underlying_tag.raw() != animated_tag.raw() { + return handled_without_value(); + } + let value = composite_scalar_value(underlying_value.data(), animated_value.data(), operation); + if !value.handled { + return handled_without_value(); + } + if value.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::OpenTypeTagged { + mode: OPEN_TYPE_MODE_FONT_VARIATION_SETTINGS, + tag: unsafe { RetainedUtf16FlyString::from_borrowed_raw(underlying_tag.raw()) }, + packed_tag: *underlying_packed_tag, + value: unsafe { RetainedStyleValueData::from_retained_pointer(value.value) }, + }) + } + ( + StyleValueData::Function { + name: underlying_name, + value: underlying_value, + }, + StyleValueData::Function { + name: animated_name, + value: animated_value, + }, + ) => { + // https://drafts.csswg.org/web-animations-1/#animating-properties + // Corresponding individual components of the computed values are combined (interpolated, added, or accumulated) using the indicated procedure for that value type (see CSS Values 4 § 3 Combining Values: Interpolation, Addition, and Accumulation). + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + if underlying_name.raw() != animated_name.raw() { + return handled_without_value(); + } + let value = composite_scalar_value(underlying_value.data(), animated_value.data(), operation); + if !value.handled { + return handled_without_value(); + } + if value.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::Function { + name: unsafe { RetainedUtf16FlyString::from_borrowed_raw(underlying_name.raw()) }, + value: unsafe { RetainedStyleValueData::from_retained_pointer(value.value) }, + }) + } + ( + StyleValueData::TextIndent { + length_percentage: underlying, + hanging: underlying_hanging, + each_line: underlying_each_line, + }, + StyleValueData::TextIndent { + length_percentage: animated, + hanging: animated_hanging, + each_line: animated_each_line, + }, + ) => { + if underlying_hanging != animated_hanging || underlying_each_line != animated_each_line { + return handled_without_value(); + } + let result = composite_scalar_value(underlying.data(), animated.data(), operation); + if !result.handled { + return handled_without_value(); + } + if result.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::TextIndent { + length_percentage: unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }, + hanging: *underlying_hanging, + each_line: *underlying_each_line, + }) + } + (StyleValueData::Ratio { .. }, StyleValueData::Ratio { .. }) => { + // https://drafts.csswg.org/css-values-4/#combine-ratio + // Addition of s is not possible. + handled_without_value() + } + ( + StyleValueData::ValueList { + values: underlying_values, + separator: underlying_separator, + .. + }, + StyleValueData::ValueList { + values: animated_values, + separator: animated_separator, + collapsible, + }, + ) => { + if underlying_values.as_slice().len() != animated_values.as_slice().len() + || underlying_separator != animated_separator + { + return handled_without_value(); + } + + let mut values = Vec::with_capacity(underlying_values.as_slice().len()); + for (underlying, animated) in underlying_values.as_slice().iter().zip(animated_values.as_slice()) { + let result = composite_scalar_value(underlying.data(), animated.data(), operation); + if !result.handled { + return handled_without_value(); + } + if result.value.is_null() { + return handled_without_value(); + } + values.push(unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }); + } + owned(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(values), + separator: *underlying_separator, + collapsible: *collapsible, + }) + } + ( + StyleValueData::GridTrackSizeList { + is_subgrid: underlying_is_subgrid, + entries: underlying_entries, + .. + }, + StyleValueData::GridTrackSizeList { + is_subgrid: animated_is_subgrid, + entries: animated_entries, + .. + }, + ) => { + let Some(entries) = composite_grid_track_entries( + *underlying_is_subgrid, + underlying_entries.as_slice(), + *animated_is_subgrid, + animated_entries.as_slice(), + operation, + ) else { + return handled_without_value(); + }; + owned(StyleValueData::GridTrackSizeList { + is_subgrid: false, + preserve_line_name_sets: false, + entries: RetainedGridTrackEntryList::from_retained_entries(entries), + }) + } + _ => handled_without_value(), + } +} + +fn interpolate_scalar_value( + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, + range_overrides: &[NumericRangeOverride], +) -> FfiAnimationValueResult { + if let Some(context) = animation_calculation_context(from).or_else(|| animation_calculation_context(to)) + && let (Some(from), Some(to)) = (numeric_calculation_node(from), numeric_calculation_node(to)) + && let Some((calculation, resolved_type)) = crate::calc::interpolate_calculations( + from, + to, + delta, + context.has_percentages_resolve_as, + context.resolve_as_is_number, + context.resolve_as_base, + ) + { + // https://drafts.csswg.org/css-values-4/#combine-math + // Interpolation of math functions, with each other or with numeric values and other numeric-valued functions, is defined as Vresult = calc((1 - p) * VA + p * VB). + return handled_retained_value(retained_calculation(calculation, resolved_type, &context)); + } + + // https://drafts.csswg.org/css-values-4/#combine-mixed + // The computed value of a percentage-dimension mix is defined as + // a computed percentage if the dimension component is zero + let dimension_component_is_zero = match (from, to) { + (StyleValueData::Length { value, .. }, StyleValueData::Percentage { .. }) => { + *value * (1.0 - delta as f64) == 0.0 + } + (StyleValueData::Percentage { .. }, StyleValueData::Length { value, .. }) => *value * delta as f64 == 0.0, + _ => false, + }; + if dimension_component_is_zero { + let percentage = match (from, to) { + (StyleValueData::Length { .. }, StyleValueData::Percentage { value }) => *value * delta as f64, + (StyleValueData::Percentage { value }, StyleValueData::Length { .. }) => *value * (1.0 - delta as f64), + _ => unreachable!(), + }; + return owned(StyleValueData::Percentage { value: percentage }); + } + + if (matches!(from, StyleValueData::Calculated { .. }) + || matches!(to, StyleValueData::Calculated { .. }) + || std::mem::discriminant(from) != std::mem::discriminant(to)) + && let (Some(from), Some(to)) = ( + length_percentage_calculation_node(from), + length_percentage_calculation_node(to), + ) + && let Some((calculation, resolved_type)) = + crate::calc::interpolate_length_percentage_calculations(from, to, delta) + { + return handled_retained_value(retained_length_percentage_calculation(calculation, resolved_type)); + } + + match (from, to) { + (StyleValueData::ColorFunction { .. }, StyleValueData::ColorFunction { .. }) => { + interpolate_legacy_rgb(from, to, delta) + .or_else(|| interpolate_modern_color(from, to, delta)) + .map_or_else(not_handled, owned) + } + (StyleValueData::BasicShape { .. }, StyleValueData::BasicShape { .. }) => { + interpolate_basic_shape(property_id, from, to, delta).map_or_else(handled_without_value, owned) + } + ( + StyleValueData::RadialSize { + component_count: from_component_count, + is_extent_0: from_is_extent_0, + value_0: from_value_0, + is_extent_1: from_is_extent_1, + value_1: from_value_1, + .. + }, + StyleValueData::RadialSize { + component_count: to_component_count, + is_extent_0: to_is_extent_0, + value_0: to_value_0, + is_extent_1: to_is_extent_1, + value_1: to_value_1, + .. + }, + ) => interpolate_radial_size( + property_id, + RadialSizeComponents { + component_count: *from_component_count, + is_extent_0: *from_is_extent_0, + value_0: from_value_0, + is_extent_1: *from_is_extent_1, + value_1: from_value_1, + }, + RadialSizeComponents { + component_count: *to_component_count, + is_extent_0: *to_is_extent_0, + value_0: to_value_0, + is_extent_1: *to_is_extent_1, + value_1: to_value_1, + }, + delta, + ) + .map_or_else(handled_without_value, owned), + (from_value @ StyleValueData::Keyword { keyword: from }, StyleValueData::Keyword { keyword: to }) + if from == to => + { + FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(from_value) }, + handled: true, + } + } + (StyleValueData::Number { value: from }, StyleValueData::Number { value: to }) => { + owned(StyleValueData::Number { + value: interpolate_f64( + *from, + *to, + delta, + accepted_range(property_id, VALUE_TYPE_NUMBER, range_overrides), + ), + }) + } + (StyleValueData::Integer { value: from }, StyleValueData::Integer { value: to }) => { + owned(StyleValueData::Integer { + value: interpolate_i32( + *from, + *to, + delta, + accepted_range(property_id, VALUE_TYPE_INTEGER, range_overrides), + ), + }) + } + ( + StyleValueData::Angle { + value: from, + unit: from_unit, + }, + StyleValueData::Angle { + value: to, + unit: to_unit, + }, + ) => { + let (Some(from), Some(to)) = (angle_to_degrees(*from, *from_unit), angle_to_degrees(*to, *to_unit)) else { + return not_handled(); + }; + owned(StyleValueData::Angle { + value: interpolate_f64( + from, + to, + delta, + accepted_range(property_id, VALUE_TYPE_ANGLE, range_overrides), + ), + unit: 0, + }) + } + ( + StyleValueData::Flex { + value: from, + unit: from_unit, + }, + StyleValueData::Flex { + value: to, + unit: to_unit, + }, + ) if from_unit == to_unit => owned(StyleValueData::Flex { + value: interpolate_f64( + *from, + *to, + delta, + accepted_range(property_id, VALUE_TYPE_FLEX, range_overrides), + ), + unit: *from_unit, + }), + ( + StyleValueData::Frequency { + value: from, + unit: from_unit, + }, + StyleValueData::Frequency { + value: to, + unit: to_unit, + }, + ) if from_unit == to_unit => owned(StyleValueData::Frequency { + value: interpolate_f64( + *from, + *to, + delta, + accepted_range(property_id, VALUE_TYPE_FREQUENCY, range_overrides), + ), + unit: *from_unit, + }), + ( + StyleValueData::Length { + value: from, + unit: from_unit, + }, + StyleValueData::Length { + value: to, + unit: to_unit, + }, + ) if from_unit == to_unit => owned(StyleValueData::Length { + value: interpolate_f64( + *from, + *to, + delta, + accepted_range(property_id, VALUE_TYPE_LENGTH, range_overrides), + ), + unit: *from_unit, + }), + (StyleValueData::Percentage { value: from }, StyleValueData::Percentage { value: to }) => { + owned(StyleValueData::Percentage { + value: interpolate_f64( + *from, + *to, + delta, + accepted_range(property_id, VALUE_TYPE_PERCENTAGE, range_overrides), + ), + }) + } + ( + StyleValueData::Resolution { + value: from, + unit: from_unit, + }, + StyleValueData::Resolution { + value: to, + unit: to_unit, + }, + ) if from_unit == to_unit => owned(StyleValueData::Resolution { + value: interpolate_f64( + *from, + *to, + delta, + accepted_range(property_id, VALUE_TYPE_RESOLUTION, range_overrides), + ), + unit: *from_unit, + }), + ( + StyleValueData::Time { + value: from, + unit: from_unit, + }, + StyleValueData::Time { + value: to, + unit: to_unit, + }, + ) if from_unit == to_unit => owned(StyleValueData::Time { + value: interpolate_f64( + *from, + *to, + delta, + accepted_range(property_id, VALUE_TYPE_TIME, range_overrides), + ), + unit: *from_unit, + }), + (StyleValueData::OpacityValue { value: from }, StyleValueData::OpacityValue { value: to }) => { + let (StyleValueData::Number { value: from }, StyleValueData::Number { value: to }) = + (from.data(), to.data()) + else { + return not_handled(); + }; + let number = Arc::into_raw(Arc::new(StyleValueData::Number { + value: interpolate_f64(*from, *to, delta, Some((0.0, 1.0))), + })); + owned(StyleValueData::OpacityValue { + value: unsafe { RetainedStyleValueData::from_retained_pointer(number) }, + }) + } + (StyleValueData::Superellipse { parameter: from }, StyleValueData::Superellipse { parameter: to }) => { + let (StyleValueData::Number { value: from }, StyleValueData::Number { value: to }) = + (from.data(), to.data()) + else { + return not_handled(); + }; + + // https://drafts.csswg.org/css-borders-4/#corner-shape-interpolation + let from_normalized_value = normalized_super_ellipse_half_corner(*from); + let to_normalized_value = normalized_super_ellipse_half_corner(*to); + let interpolated_value = + interpolate_f64(from_normalized_value, to_normalized_value, delta, Some((0.0, 1.0))); + let parameter = Arc::into_raw(Arc::new(StyleValueData::Number { + value: interpolation_value_to_super_ellipse_parameter(interpolated_value), + })); + owned(StyleValueData::Superellipse { + parameter: unsafe { RetainedStyleValueData::from_retained_pointer(parameter) }, + }) + } + ( + StyleValueData::BackgroundSize { + size_x: from_x, + size_y: from_y, + }, + StyleValueData::BackgroundSize { + size_x: to_x, + size_y: to_y, + }, + ) => { + let x = interpolate_scalar_value(property_id, from_x.data(), to_x.data(), delta, range_overrides); + let y = interpolate_scalar_value(property_id, from_y.data(), to_y.data(), delta, range_overrides); + if !x.handled || !y.handled { + return not_handled(); + } + if x.value.is_null() || y.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::BackgroundSize { + size_x: unsafe { RetainedStyleValueData::from_retained_pointer(x.value) }, + size_y: unsafe { RetainedStyleValueData::from_retained_pointer(y.value) }, + }) + } + (StyleValueData::Edge { offset: from, .. }, StyleValueData::Edge { offset: to, .. }) => { + let (Some(from), Some(to)) = (from.optional_data(), to.optional_data()) else { + return not_handled(); + }; + let Some(offset) = interpolate_translate_component(property_id, from, to, delta) else { + return handled_without_value(); + }; + owned(StyleValueData::Edge { + has_edge: false, + edge: 0, + offset, + }) + } + ( + StyleValueData::Position { + edge_x: from_x, + edge_y: from_y, + }, + StyleValueData::Position { + edge_x: to_x, + edge_y: to_y, + }, + ) => { + // https://www.w3.org/TR/css-values-4/#combine-positions + // FIXME: Interpolation of is defined as the independent interpolation of each component (x, y) normalized as an offset from the top left corner as a . + let x = interpolate_scalar_value(property_id, from_x.data(), to_x.data(), delta, range_overrides); + let y = interpolate_scalar_value(property_id, from_y.data(), to_y.data(), delta, range_overrides); + if !x.handled || !y.handled { + return not_handled(); + } + if x.value.is_null() || y.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::Position { + edge_x: unsafe { RetainedStyleValueData::from_retained_pointer(x.value) }, + edge_y: unsafe { RetainedStyleValueData::from_retained_pointer(y.value) }, + }) + } + ( + StyleValueData::Rect { + top: from_top, + right: from_right, + bottom: from_bottom, + left: from_left, + }, + StyleValueData::Rect { + top: to_top, + right: to_right, + bottom: to_bottom, + left: to_left, + }, + ) => { + let combine = |from: &RetainedStyleValueData, to: &RetainedStyleValueData| { + let result = interpolate_scalar_value(property_id, from.data(), to.data(), delta, range_overrides); + if !result.handled { + return Err(()); + } + if result.value.is_null() { + return Ok(None); + } + Ok(Some(unsafe { + RetainedStyleValueData::from_retained_pointer(result.value) + })) + }; + let value = (|| { + let Some(top) = combine(from_top, to_top)? else { + return Ok(None); + }; + let Some(right) = combine(from_right, to_right)? else { + return Ok(None); + }; + let Some(bottom) = combine(from_bottom, to_bottom)? else { + return Ok(None); + }; + let Some(left) = combine(from_left, to_left)? else { + return Ok(None); + }; + Ok(Some(StyleValueData::Rect { + top, + right, + bottom, + left, + })) + })(); + match value { + Err(()) => not_handled(), + Ok(None) => handled_without_value(), + Ok(Some(value)) => owned(value), + } + } + ( + StyleValueData::BorderRadius { + horizontal_radius: from_horizontal, + vertical_radius: from_vertical, + .. + }, + StyleValueData::BorderRadius { + horizontal_radius: to_horizontal, + vertical_radius: to_vertical, + .. + }, + ) => { + let horizontal = interpolate_scalar_value( + property_id, + from_horizontal.data(), + to_horizontal.data(), + delta, + range_overrides, + ); + if !horizontal.handled { + return not_handled(); + } + if horizontal.value.is_null() { + return handled_without_value(); + } + let horizontal = unsafe { RetainedStyleValueData::from_retained_pointer(horizontal.value) }; + let vertical = interpolate_scalar_value( + property_id, + from_vertical.data(), + to_vertical.data(), + delta, + range_overrides, + ); + if !vertical.handled { + return not_handled(); + } + if vertical.value.is_null() { + return handled_without_value(); + } + let vertical = unsafe { RetainedStyleValueData::from_retained_pointer(vertical.value) }; + owned(StyleValueData::BorderRadius { + is_elliptical: !radius_components_equal(horizontal.data(), vertical.data()), + horizontal_radius: horizontal, + vertical_radius: vertical, + }) + } + ( + StyleValueData::BorderRadiusRect { + top_left: from_top_left, + top_right: from_top_right, + bottom_right: from_bottom_right, + bottom_left: from_bottom_left, + }, + StyleValueData::BorderRadiusRect { + top_left: to_top_left, + top_right: to_top_right, + bottom_right: to_bottom_right, + bottom_left: to_bottom_left, + }, + ) => { + // https://drafts.csswg.org/web-animations-1/#animating-properties + // Corresponding individual components of the computed values are combined (interpolated, added, or accumulated) using the indicated procedure for that value type (see CSS Values 4 § 3 Combining Values: Interpolation, Addition, and Accumulation). + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + let combine = |from: &RetainedStyleValueData, to: &RetainedStyleValueData| { + let result = + interpolate_scalar_value(property_id, from.data(), to.data(), delta, BORDER_RADIUS_RECT_RANGES); + if !result.handled { + return Err(()); + } + if result.value.is_null() { + return Ok(None); + } + Ok(Some(unsafe { + RetainedStyleValueData::from_retained_pointer(result.value) + })) + }; + let value = (|| { + let Some(top_left) = combine(from_top_left, to_top_left)? else { + return Ok(None); + }; + let Some(top_right) = combine(from_top_right, to_top_right)? else { + return Ok(None); + }; + let Some(bottom_right) = combine(from_bottom_right, to_bottom_right)? else { + return Ok(None); + }; + let Some(bottom_left) = combine(from_bottom_left, to_bottom_left)? else { + return Ok(None); + }; + Ok(Some(StyleValueData::BorderRadiusRect { + top_left, + top_right, + bottom_right, + bottom_left, + })) + })(); + match value { + Err(()) => not_handled(), + Ok(None) => handled_without_value(), + Ok(Some(value)) => owned(value), + } + } + ( + StyleValueData::BorderImageSlice { + top: from_top, + right: from_right, + bottom: from_bottom, + left: from_left, + fill: from_fill, + }, + StyleValueData::BorderImageSlice { + top: to_top, + right: to_right, + bottom: to_bottom, + left: to_left, + fill: to_fill, + }, + ) => { + // https://drafts.csswg.org/web-animations-1/#animating-properties + // Corresponding individual components of the computed values are combined (interpolated, added, or accumulated) using the indicated procedure for that value type (see CSS Values 4 § 3 Combining Values: Interpolation, Addition, and Accumulation). + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + if from_fill != to_fill { + return handled_without_value(); + } + let combine = |from: &RetainedStyleValueData, to: &RetainedStyleValueData| { + let result = interpolate_scalar_value(property_id, from.data(), to.data(), delta, range_overrides); + if !result.handled { + return Err(()); + } + if result.value.is_null() { + return Ok(None); + } + Ok(Some(unsafe { + RetainedStyleValueData::from_retained_pointer(result.value) + })) + }; + let value = (|| { + let Some(top) = combine(from_top, to_top)? else { + return Ok(None); + }; + let Some(right) = combine(from_right, to_right)? else { + return Ok(None); + }; + let Some(bottom) = combine(from_bottom, to_bottom)? else { + return Ok(None); + }; + let Some(left) = combine(from_left, to_left)? else { + return Ok(None); + }; + Ok(Some(StyleValueData::BorderImageSlice { + top, + right, + bottom, + left, + fill: *from_fill, + })) + })(); + match value { + Err(()) => not_handled(), + Ok(None) => handled_without_value(), + Ok(Some(value)) => owned(value), + } + } + ( + StyleValueData::OpenTypeTagged { + tag: from_tag, + packed_tag: from_packed_tag, + value: from_value, + .. + }, + StyleValueData::OpenTypeTagged { + tag: to_tag, + value: to_value, + .. + }, + ) => { + // https://drafts.csswg.org/web-animations-1/#animating-properties + // Corresponding individual components of the computed values are combined (interpolated, added, or accumulated) using the indicated procedure for that value type (see CSS Values 4 § 3 Combining Values: Interpolation, Addition, and Accumulation). + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + if from_tag.raw() != to_tag.raw() { + return handled_without_value(); + } + let value = + interpolate_scalar_value(property_id, from_value.data(), to_value.data(), delta, range_overrides); + if !value.handled { + return not_handled(); + } + if value.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::OpenTypeTagged { + mode: OPEN_TYPE_MODE_FONT_VARIATION_SETTINGS, + tag: unsafe { RetainedUtf16FlyString::from_borrowed_raw(from_tag.raw()) }, + packed_tag: *from_packed_tag, + value: unsafe { RetainedStyleValueData::from_retained_pointer(value.value) }, + }) + } + ( + StyleValueData::Function { + name: from_name, + value: from_value, + }, + StyleValueData::Function { + name: to_name, + value: to_value, + }, + ) => { + // https://drafts.csswg.org/web-animations-1/#animating-properties + // Corresponding individual components of the computed values are combined (interpolated, added, or accumulated) using the indicated procedure for that value type (see CSS Values 4 § 3 Combining Values: Interpolation, Addition, and Accumulation). + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + if from_name.raw() != to_name.raw() { + return handled_without_value(); + } + let value = + interpolate_scalar_value(property_id, from_value.data(), to_value.data(), delta, range_overrides); + if !value.handled { + return not_handled(); + } + if value.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::Function { + name: unsafe { RetainedUtf16FlyString::from_borrowed_raw(from_name.raw()) }, + value: unsafe { RetainedStyleValueData::from_retained_pointer(value.value) }, + }) + } + ( + StyleValueData::TextIndent { + length_percentage: from, + hanging: from_hanging, + each_line: from_each_line, + }, + StyleValueData::TextIndent { + length_percentage: to, + hanging: to_hanging, + each_line: to_each_line, + }, + ) => { + if from_hanging != to_hanging || from_each_line != to_each_line { + return handled_without_value(); + } + let result = interpolate_scalar_value(property_id, from.data(), to.data(), delta, range_overrides); + if !result.handled { + return not_handled(); + } + if result.value.is_null() { + return handled_without_value(); + } + owned(StyleValueData::TextIndent { + length_percentage: unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }, + hanging: *from_hanging, + each_line: *from_each_line, + }) + } + ( + StyleValueData::Ratio { + numerator: from_numerator, + denominator: from_denominator, + }, + StyleValueData::Ratio { + numerator: to_numerator, + denominator: to_denominator, + }, + ) => { + let ( + StyleValueData::Number { value: from_numerator }, + StyleValueData::Number { + value: from_denominator, + }, + StyleValueData::Number { value: to_numerator }, + StyleValueData::Number { value: to_denominator }, + ) = ( + from_numerator.data(), + from_denominator.data(), + to_numerator.data(), + to_denominator.data(), + ) + else { + return not_handled(); + }; + + // https://drafts.csswg.org/css-values/#combine-ratio + // If either is degenerate, the values cannot be interpolated. + if !from_numerator.is_finite() + || *from_numerator == 0.0 + || !from_denominator.is_finite() + || *from_denominator == 0.0 + || !to_numerator.is_finite() + || *to_numerator == 0.0 + || !to_denominator.is_finite() + || *to_denominator == 0.0 + { + return handled_without_value(); + } + + // The interpolation of a is defined by converting each to a number by dividing the first value + // by the second (so a ratio of 3 / 2 would become 1.5), taking the logarithm of that result (so the 1.5 would + // become approximately 0.176), then interpolating those values. The result during the interpolation is + // converted back to a by inverting the logarithm, then interpreting the result as a with the + // result as the first value and 1 as the second value. + let from_number = (from_numerator / from_denominator).ln(); + let to_number = (to_numerator / to_denominator).ln(); + let value = interpolate_f64( + from_number, + to_number, + delta, + accepted_range(property_id, VALUE_TYPE_RATIO, range_overrides), + ) + .exp(); + let numerator = Arc::into_raw(Arc::new(StyleValueData::Number { value })); + let denominator = Arc::into_raw(Arc::new(StyleValueData::Number { value: 1.0 })); + owned(StyleValueData::Ratio { + numerator: unsafe { RetainedStyleValueData::from_retained_pointer(numerator) }, + denominator: unsafe { RetainedStyleValueData::from_retained_pointer(denominator) }, + }) + } + ( + StyleValueData::ValueList { + values: from_values, + separator, + collapsible, + }, + StyleValueData::ValueList { values: to_values, .. }, + ) => { + // https://www.w3.org/TR/web-animations/#by-computed-value + // If the number of components or the types of corresponding components do not match, + // or if any component value uses discrete animation and the two corresponding values do not match, + // then the property values combine as discrete. + if from_values.as_slice().len() != to_values.as_slice().len() { + return not_handled(); + } + + let mut values = Vec::with_capacity(from_values.as_slice().len()); + for (from, to) in from_values.as_slice().iter().zip(to_values.as_slice()) { + let result = interpolate_scalar_value(property_id, from.data(), to.data(), delta, range_overrides); + if !result.handled { + return not_handled(); + } + if result.value.is_null() { + return handled_without_value(); + } + values.push(unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }); + } + owned(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(values), + separator: *separator, + collapsible: *collapsible, + }) + } + _ => not_handled(), + } +} + +fn interpolate_rotate_3d( + property: u16, + transform_function: u8, + from_arguments: &RetainedStyleValueDataList, + to_arguments: &RetainedStyleValueDataList, + delta: f32, +) -> Option { + let ([from_x, from_y, from_z, from_angle], [to_x, to_y, to_z, to_angle]) = + (from_arguments.as_slice(), to_arguments.as_slice()) + else { + return None; + }; + let ( + StyleValueData::Number { value: from_x }, + StyleValueData::Number { value: from_y }, + StyleValueData::Number { value: from_z }, + StyleValueData::Angle { + value: from_angle, + unit: from_angle_unit, + }, + StyleValueData::Number { value: to_x }, + StyleValueData::Number { value: to_y }, + StyleValueData::Number { value: to_z }, + StyleValueData::Angle { + value: to_angle, + unit: to_angle_unit, + }, + ) = ( + from_x.data(), + from_y.data(), + from_z.data(), + from_angle.data(), + to_x.data(), + to_y.data(), + to_z.data(), + to_angle.data(), + ) + else { + return None; + }; + let from_angle = angle_to_degrees(*from_angle, *from_angle_unit)?.to_radians(); + let to_angle = angle_to_degrees(*to_angle, *to_angle_unit)?.to_radians(); + let from_axis = [*from_x, *from_y, *from_z]; + let to_axis = [*to_x, *to_y, *to_z]; + + let length = |vector: [f64; 3]| vector.iter().map(|component| component * component).sum::().sqrt(); + let normalize = |vector: [f64; 3]| { + let length = length(vector); + [vector[0] / length, vector[1] / length, vector[2] / length] + }; + let epsilon = 1e-5; + let from_axis_normalized = if length(from_axis) > epsilon { + normalize(from_axis) + } else { + [0.0, 0.0, 1.0] + }; + let to_axis_normalized = if length(to_axis) > epsilon { + normalize(to_axis) + } else { + [0.0, 0.0, 1.0] + }; + let axis_difference = [ + from_axis_normalized[0] - to_axis_normalized[0], + from_axis_normalized[1] - to_axis_normalized[1], + from_axis_normalized[2] - to_axis_normalized[2], + ]; + + // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions + // For interpolations with the primitive rotate3d(), the direction vectors of the transform functions get + // normalized first. If the normalized vectors are not equal and both rotation angles are non-zero the + // transform functions get converted into 4x4 matrices first and interpolated as defined in section + // Interpolation of Matrices afterwards. Otherwise the rotation angle gets interpolated numerically and the + // rotation vector of the non-zero angle is used or (0, 0, 1) if both angles are zero. + let (result_axis, result_angle) = if length(axis_difference) < epsilon || from_angle == 0.0 || to_angle == 0.0 { + let result_axis = if to_angle != 0.0 { + to_axis_normalized + } else if from_angle != 0.0 { + from_axis_normalized + } else { + [0.0, 0.0, 1.0] + }; + (result_axis, interpolate_f64(from_angle, to_angle, delta, None)) + } else { + let to_quaternion = |axis: [f64; 3], angle: f64| { + let half_angle = angle / 2.0; + let sin_half_angle = half_angle.sin(); + [ + axis[0] * sin_half_angle, + axis[1] * sin_half_angle, + axis[2] * sin_half_angle, + half_angle.cos(), + ] + }; + let from_quaternion = to_quaternion(from_axis_normalized, from_angle); + let to_quaternion = to_quaternion(to_axis_normalized, to_angle); + + // https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values + let product = from_quaternion + .iter() + .zip(to_quaternion) + .map(|(from, to)| from * to) + .sum::() + .clamp(-1.0, 1.0); + let interpolated_quaternion = if product.abs() >= 1.0 { + from_quaternion + } else { + let theta = product.acos(); + let weight = (f64::from(delta) * theta).sin() / (1.0 - product * product).sqrt(); + let from_multiplier = (f64::from(delta) * theta).cos() - product * weight; + if weight.abs() < f64::from(f32::EPSILON) { + from_quaternion.map(|component| component * from_multiplier) + } else if from_multiplier.abs() < f64::from(f32::EPSILON) { + to_quaternion.map(|component| component * weight) + } else { + std::array::from_fn(|index| from_quaternion[index] * from_multiplier + to_quaternion[index] * weight) + } + }; + + let mut axis = [ + interpolated_quaternion[0], + interpolated_quaternion[1], + interpolated_quaternion[2], + ]; + let sin_half_angle = (1.0 - interpolated_quaternion[3] * interpolated_quaternion[3]) + .max(0.0) + .sqrt(); + let angle = 2.0 * interpolated_quaternion[3].clamp(-1.0, 1.0).acos(); + if sin_half_angle >= epsilon { + axis = axis.map(|component| component / sin_half_angle); + } + (axis, angle) + }; + + let mut arguments = Vec::with_capacity(4); + for value in result_axis { + let argument = Arc::into_raw(Arc::new(StyleValueData::Number { value })); + arguments.push(unsafe { RetainedStyleValueData::from_retained_pointer(argument) }); + } + let angle = Arc::into_raw(Arc::new(StyleValueData::Angle { + value: result_angle.to_degrees(), + unit: 0, + })); + arguments.push(unsafe { RetainedStyleValueData::from_retained_pointer(angle) }); + + Some(StyleValueData::Transformation { + property, + transform_function, + values: RetainedStyleValueDataList::from_retained_values(arguments), + }) +} + +fn retained_number(value: f64) -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Number { value })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn retained_zero_px() -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Length { + value: 0.0, + unit: crate::style_compute::px_length_unit(), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn retained_length(value: f64, unit: u8) -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Length { value, unit })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn retained_none_keyword() -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Keyword { + keyword: crate::style_compute::none_keyword(), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn is_2d_transform(function: u8) -> bool { + matches!( + function, + TRANSFORM_FUNCTION_ROTATE + | TRANSFORM_FUNCTION_SCALE + | TRANSFORM_FUNCTION_SCALE_X + | TRANSFORM_FUNCTION_SCALE_Y + | TRANSFORM_FUNCTION_TRANSLATE + | TRANSFORM_FUNCTION_TRANSLATE_X + | TRANSFORM_FUNCTION_TRANSLATE_Y + ) +} + +fn is_3d_primitive(function: u8) -> bool { + matches!( + function, + TRANSFORM_FUNCTION_ROTATE_3D | TRANSFORM_FUNCTION_SCALE_3D | TRANSFORM_FUNCTION_TRANSLATE_3D + ) +} + +fn is_3d_transform(function: u8) -> bool { + is_2d_transform(function) + || is_3d_primitive(function) + || matches!( + function, + TRANSFORM_FUNCTION_ROTATE_X + | TRANSFORM_FUNCTION_ROTATE_Y + | TRANSFORM_FUNCTION_ROTATE_Z + | TRANSFORM_FUNCTION_SCALE_Z + | TRANSFORM_FUNCTION_TRANSLATE_Z + ) +} + +fn convert_2d_transform_to_primitive( + function: u8, + arguments: &RetainedStyleValueDataList, +) -> Option<(u8, Vec)> { + let arguments = arguments.as_slice(); + match (function, arguments) { + (TRANSFORM_FUNCTION_SCALE, [x]) => { + Some((TRANSFORM_FUNCTION_SCALE, vec![x.clone_retained(), x.clone_retained()])) + } + (TRANSFORM_FUNCTION_SCALE, [x, y]) => { + Some((TRANSFORM_FUNCTION_SCALE, vec![x.clone_retained(), y.clone_retained()])) + } + (TRANSFORM_FUNCTION_SCALE_X, [x]) => { + Some((TRANSFORM_FUNCTION_SCALE, vec![x.clone_retained(), retained_number(1.0)])) + } + (TRANSFORM_FUNCTION_SCALE_Y, [y]) => { + Some((TRANSFORM_FUNCTION_SCALE, vec![retained_number(1.0), y.clone_retained()])) + } + (TRANSFORM_FUNCTION_ROTATE, [angle]) => Some((TRANSFORM_FUNCTION_ROTATE, vec![angle.clone_retained()])), + (TRANSFORM_FUNCTION_TRANSLATE, [x]) => Some(( + TRANSFORM_FUNCTION_TRANSLATE, + vec![x.clone_retained(), retained_zero_px()], + )), + (TRANSFORM_FUNCTION_TRANSLATE, [x, y]) => Some(( + TRANSFORM_FUNCTION_TRANSLATE, + vec![x.clone_retained(), y.clone_retained()], + )), + (TRANSFORM_FUNCTION_TRANSLATE_X, [x]) => Some(( + TRANSFORM_FUNCTION_TRANSLATE, + vec![x.clone_retained(), retained_zero_px()], + )), + (TRANSFORM_FUNCTION_TRANSLATE_Y, [y]) => Some(( + TRANSFORM_FUNCTION_TRANSLATE, + vec![retained_zero_px(), y.clone_retained()], + )), + _ => None, + } +} + +// https://drafts.csswg.org/css-transforms-1/#transform-primitives +// https://drafts.csswg.org/css-transforms-2/#transform-primitives +fn convert_3d_transform_to_primitive( + function: u8, + arguments: &RetainedStyleValueDataList, +) -> Option<(u8, Vec)> { + let converted_2d; + let (function, arguments) = if is_2d_transform(function) { + converted_2d = convert_2d_transform_to_primitive(function, arguments)?; + (converted_2d.0, converted_2d.1.as_slice()) + } else { + (function, arguments.as_slice()) + }; + + match (function, arguments) { + (TRANSFORM_FUNCTION_ROTATE | TRANSFORM_FUNCTION_ROTATE_Z, [angle]) => Some(( + TRANSFORM_FUNCTION_ROTATE_3D, + vec![ + retained_number(0.0), + retained_number(0.0), + retained_number(1.0), + angle.clone_retained(), + ], + )), + (TRANSFORM_FUNCTION_ROTATE_X, [angle]) => Some(( + TRANSFORM_FUNCTION_ROTATE_3D, + vec![ + retained_number(1.0), + retained_number(0.0), + retained_number(0.0), + angle.clone_retained(), + ], + )), + (TRANSFORM_FUNCTION_ROTATE_Y, [angle]) => Some(( + TRANSFORM_FUNCTION_ROTATE_3D, + vec![ + retained_number(0.0), + retained_number(1.0), + retained_number(0.0), + angle.clone_retained(), + ], + )), + (TRANSFORM_FUNCTION_SCALE, [x, y]) => Some(( + TRANSFORM_FUNCTION_SCALE_3D, + vec![x.clone_retained(), y.clone_retained(), retained_number(1.0)], + )), + (TRANSFORM_FUNCTION_SCALE_Z, [z]) => Some(( + TRANSFORM_FUNCTION_SCALE_3D, + vec![retained_number(1.0), retained_number(1.0), z.clone_retained()], + )), + (TRANSFORM_FUNCTION_TRANSLATE, [x, y]) => Some(( + TRANSFORM_FUNCTION_TRANSLATE_3D, + vec![x.clone_retained(), y.clone_retained(), retained_zero_px()], + )), + (TRANSFORM_FUNCTION_TRANSLATE_Z, [z]) => Some(( + TRANSFORM_FUNCTION_TRANSLATE_3D, + vec![retained_zero_px(), retained_zero_px(), z.clone_retained()], + )), + _ => None, + } +} + +fn convert_transform_pair_to_common_primitive( + from_function: u8, + from_arguments: &RetainedStyleValueDataList, + to_function: u8, + to_arguments: &RetainedStyleValueDataList, +) -> Option<(u8, Vec, Vec)> { + if matches!( + (from_function, to_function), + ( + TRANSFORM_FUNCTION_MATRIX | TRANSFORM_FUNCTION_MATRIX_3D | TRANSFORM_FUNCTION_PERSPECTIVE, + _, + ) | ( + _, + TRANSFORM_FUNCTION_MATRIX | TRANSFORM_FUNCTION_MATRIX_3D | TRANSFORM_FUNCTION_PERSPECTIVE, + ) + ) { + return None; + } + // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions + // If both transform functions share a primitive in the two-dimensional space, both transform functions get + // converted to the two-dimensional primitive. If one or both transform functions are three-dimensional + // transform functions, the common three-dimensional primitive is used. + let (from_function, from_arguments, to_function, to_arguments) = + if is_2d_transform(from_function) && is_2d_transform(to_function) { + let (from_function, from_arguments) = convert_2d_transform_to_primitive(from_function, from_arguments)?; + let (to_function, to_arguments) = convert_2d_transform_to_primitive(to_function, to_arguments)?; + (from_function, from_arguments, to_function, to_arguments) + } else if is_3d_transform(from_function) || is_3d_transform(to_function) { + let (from_function, from_arguments) = if is_3d_primitive(from_function) { + ( + from_function, + from_arguments + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + ) + } else { + convert_3d_transform_to_primitive(from_function, from_arguments)? + }; + let (to_function, to_arguments) = if is_3d_primitive(to_function) { + ( + to_function, + to_arguments + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + ) + } else { + convert_3d_transform_to_primitive(to_function, to_arguments)? + }; + (from_function, from_arguments, to_function, to_arguments) + } else { + ( + from_function, + from_arguments + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + to_function, + to_arguments + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + ) + }; + (from_function == to_function && from_arguments.len() == to_arguments.len()).then_some(( + from_function, + from_arguments, + to_arguments, + )) +} + +fn identity_transformation(property: u16, function: u8) -> Option { + // https://drafts.csswg.org/css-transforms-1/#identity-transform-function + // A transform function that is equivalent to a identity 4x4 matrix (see Mathematical Description of Transform + // Functions). Examples for identity transform functions are translate(0), translateX(0), translateY(0), scale(1), + // scaleX(1), scaleY(1), rotate(0), skew(0, 0), skewX(0), skewY(0) and matrix(1, 0, 0, 1, 0, 0). + + // https://drafts.csswg.org/css-transforms-2/#identity-transform-function + // In addition to the identity transform function in CSS Transforms, examples for identity transform functions + // include translate3d(0, 0, 0), translateZ(0), scaleZ(1), rotate3d(1, 1, 1, 0), rotateX(0), rotateY(0), rotateZ(0) + // and matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1). A special case is perspective: perspective(none). + // The value of m34 becomes infinitesimal small and the transform function is therefore assumed to be equal to the + // identity matrix. + let arguments = match function { + TRANSFORM_FUNCTION_MATRIX => vec![ + retained_number(1.0), + retained_number(0.0), + retained_number(0.0), + retained_number(1.0), + retained_number(0.0), + retained_number(0.0), + ], + TRANSFORM_FUNCTION_MATRIX_3D => (0..16) + .map(|index| retained_number(if index % 5 == 0 { 1.0 } else { 0.0 })) + .collect(), + TRANSFORM_FUNCTION_PERSPECTIVE => vec![retained_none_keyword()], + TRANSFORM_FUNCTION_ROTATE + | TRANSFORM_FUNCTION_ROTATE_X + | TRANSFORM_FUNCTION_ROTATE_Y + | TRANSFORM_FUNCTION_ROTATE_Z + | TRANSFORM_FUNCTION_SKEW + | TRANSFORM_FUNCTION_SKEW_X + | TRANSFORM_FUNCTION_SKEW_Y => { + let angle = Arc::into_raw(Arc::new(StyleValueData::Angle { value: 0.0, unit: 0 })); + vec![unsafe { RetainedStyleValueData::from_retained_pointer(angle) }] + } + TRANSFORM_FUNCTION_ROTATE_3D => vec![ + retained_number(1.0), + retained_number(1.0), + retained_number(1.0), + unsafe { + RetainedStyleValueData::from_retained_pointer(Arc::into_raw(Arc::new(StyleValueData::Angle { + value: 0.0, + unit: 0, + }))) + }, + ], + TRANSFORM_FUNCTION_TRANSLATE + | TRANSFORM_FUNCTION_TRANSLATE_X + | TRANSFORM_FUNCTION_TRANSLATE_Y + | TRANSFORM_FUNCTION_TRANSLATE_Z => vec![retained_zero_px()], + TRANSFORM_FUNCTION_TRANSLATE_3D => vec![retained_zero_px(), retained_zero_px(), retained_zero_px()], + TRANSFORM_FUNCTION_SCALE + | TRANSFORM_FUNCTION_SCALE_X + | TRANSFORM_FUNCTION_SCALE_Y + | TRANSFORM_FUNCTION_SCALE_Z => vec![retained_number(1.0)], + TRANSFORM_FUNCTION_SCALE_3D => vec![retained_number(1.0), retained_number(1.0), retained_number(1.0)], + _ => return None, + }; + let transformation = Arc::into_raw(Arc::new(StyleValueData::Transformation { + property, + transform_function: function, + values: RetainedStyleValueDataList::from_retained_values(arguments), + })); + Some(unsafe { RetainedStyleValueData::from_retained_pointer(transformation) }) +} + +type Matrix4 = [[f64; 4]; 4]; + +struct DecomposedMatrix { + translation: [f64; 3], + scale: [f64; 3], + skew: [f64; 3], + rotation: [f64; 4], + perspective: [f64; 4], +} + +fn identity_matrix() -> Matrix4 { + std::array::from_fn(|row| std::array::from_fn(|column| if row == column { 1.0 } else { 0.0 })) +} + +fn multiply_matrices(left: Matrix4, right: Matrix4) -> Matrix4 { + std::array::from_fn(|row| { + std::array::from_fn(|column| (0..4).map(|index| left[row][index] * right[index][column]).sum()) + }) +} + +fn invert_matrix(matrix: Matrix4) -> Option { + let mut augmented = [[0.0; 8]; 4]; + for row in 0..4 { + augmented[row][..4].copy_from_slice(&matrix[row]); + augmented[row][row + 4] = 1.0; + } + for column in 0..4 { + let pivot_row = (column..4).max_by(|left, right| { + augmented[*left][column] + .abs() + .total_cmp(&augmented[*right][column].abs()) + })?; + if augmented[pivot_row][column] == 0.0 { + return None; + } + augmented.swap(column, pivot_row); + let pivot = augmented[column][column]; + for value in &mut augmented[column] { + *value /= pivot; + } + let pivot_values = augmented[column]; + for (row, values) in augmented.iter_mut().enumerate() { + if row == column { + continue; + } + let factor = values[column]; + for index in 0..8 { + values[index] -= factor * pivot_values[index]; + } + } + } + Some(std::array::from_fn(|row| { + std::array::from_fn(|column| augmented[row][column + 4]) + })) +} + +fn vector_length(vector: [f64; 3]) -> f64 { + vector.iter().map(|component| component * component).sum::().sqrt() +} + +fn vector_dot(left: [f64; 3], right: [f64; 3]) -> f64 { + left.iter().zip(right).map(|(left, right)| left * right).sum() +} + +fn vector_cross(left: [f64; 3], right: [f64; 3]) -> [f64; 3] { + [ + left[1] * right[2] - left[2] * right[1], + left[2] * right[0] - left[0] * right[2], + left[0] * right[1] - left[1] * right[0], + ] +} + +// https://drafts.csswg.org/css-transforms-1/#supporting-functions +fn combine_vectors(left: [f64; 3], right: [f64; 3], left_scale: f64, right_scale: f64) -> [f64; 3] { + std::array::from_fn(|index| left_scale * left[index] + right_scale * right[index]) +} + +// https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix +fn decompose_matrix(mut matrix: Matrix4) -> Option { + // Normalize the matrix. + if matrix[3][3] == 0.0 { + return None; + } + let normalization = matrix[3][3]; + for row in &mut matrix { + for value in row { + *value /= normalization; + } + } + + // perspectiveMatrix is used to solve for perspective, but it also provides + // an easy way to test for singularity of the upper 3x3 component. + let mut perspective_matrix = matrix; + perspective_matrix[3][..3].fill(0.0); + perspective_matrix[3][3] = 1.0; + // Solve the equation by inverting perspectiveMatrix and multiplying + // rightHandSide by the inverse. + let inverse_perspective_matrix = invert_matrix(perspective_matrix)?; + + // First, isolate perspective. + let perspective = if matrix[3][0] != 0.0 || matrix[3][1] != 0.0 || matrix[3][2] != 0.0 { + // rightHandSide is the right hand side of the equation. + // Note: It is the bottom side in a row-major matrix + let bottom_side = matrix[3]; + std::array::from_fn(|row| { + (0..4) + .map(|column| inverse_perspective_matrix[column][row] * bottom_side[column]) + .sum() + }) + } else { + // No perspective. + [0.0, 0.0, 0.0, 1.0] + }; + + // Next take care of translation + let translation = [matrix[0][3], matrix[1][3], matrix[2][3]]; + + // Now get scale and shear. 'row' is a 3 element array of 3 component vectors + let mut row: [[f64; 3]; 3] = + std::array::from_fn(|column| [matrix[0][column], matrix[1][column], matrix[2][column]]); + + // Compute X scale factor and normalize first row. + let mut scale = [0.0; 3]; + scale[0] = vector_length(row[0]); + row[0] = row[0].map(|value| value / scale[0]); + + // Compute XY shear factor and make 2nd row orthogonal to 1st. + let mut skew = [0.0; 3]; + skew[0] = vector_dot(row[0], row[1]); + row[1] = combine_vectors(row[1], row[0], 1.0, -skew[0]); + + // Now, compute Y scale and normalize 2nd row. + scale[1] = vector_length(row[1]); + row[1] = row[1].map(|value| value / scale[1]); + skew[0] /= scale[1]; + + // Compute XZ and YZ shears, orthogonalize 3rd row + skew[1] = vector_dot(row[0], row[2]); + row[2] = combine_vectors(row[2], row[0], 1.0, -skew[1]); + skew[2] = vector_dot(row[1], row[2]); + row[2] = combine_vectors(row[2], row[1], 1.0, -skew[2]); + + // Next, get Z scale and normalize 3rd row. + scale[2] = vector_length(row[2]); + row[2] = row[2].map(|value| value / scale[2]); + skew[1] /= scale[2]; + skew[2] /= scale[2]; + + // At this point, the matrix (in rows) is orthonormal. + // Check for a coordinate system flip. If the determinant + // is -1, then negate the matrix and the scaling factors. + let pdum3 = vector_cross(row[1], row[2]); + if vector_dot(row[0], pdum3) < 0.0 { + for index in 0..3 { + scale[index] *= -1.0; + row[index] = row[index].map(|value| -value); + } + } + + // Now, get the rotations out + let mut rotation = [ + 0.5 * (1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0).sqrt(), + 0.5 * (1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0).sqrt(), + 0.5 * (1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0).sqrt(), + 0.5 * (1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0).sqrt(), + ]; + if row[2][1] > row[1][2] { + rotation[0] = -rotation[0]; + } + if row[0][2] > row[2][0] { + rotation[1] = -rotation[1]; + } + if row[1][0] > row[0][1] { + rotation[2] = -rotation[2]; + } + + // FIXME: This accounts for the fact that the browser coordinate system is left-handed instead of right-handed. + // The reason for this is that the positive Y-axis direction points down instead of up. To fix this, we + // invert the Y axis. However, it feels like the spec pseudo-code above should have taken something like + // this into account, so we're probably doing something else wrong. + rotation[2] *= -1.0; + + Some(DecomposedMatrix { + translation, + scale, + skew, + rotation, + perspective, + }) +} + +// https://drafts.csswg.org/css-transforms-2/#recomposing-to-a-3d-matrix +fn recompose_matrix(values: DecomposedMatrix) -> Matrix4 { + let mut matrix = identity_matrix(); + + // apply perspective + matrix[3] = values.perspective; + + // apply translation + for row in &mut matrix { + for column in 0..3 { + row[3] += values.translation[column] * row[column]; + } + } + + // apply rotation + let [x, y, z, w] = values.rotation; + // Construct a composite rotation matrix from the quaternion values + // rotationMatrix is a identity 4x4 matrix initially + let mut rotation_matrix = identity_matrix(); + rotation_matrix[0][0] = 1.0 - 2.0 * (y * y + z * z); + rotation_matrix[1][0] = 2.0 * (x * y - z * w); + rotation_matrix[2][0] = 2.0 * (x * z + y * w); + rotation_matrix[0][1] = 2.0 * (x * y + z * w); + rotation_matrix[1][1] = 1.0 - 2.0 * (x * x + z * z); + rotation_matrix[2][1] = 2.0 * (y * z - x * w); + rotation_matrix[0][2] = 2.0 * (x * z - y * w); + rotation_matrix[1][2] = 2.0 * (y * z + x * w); + rotation_matrix[2][2] = 1.0 - 2.0 * (x * x + y * y); + matrix = multiply_matrices(matrix, rotation_matrix); + + // apply skew + // temp is a identity 4x4 matrix initially + let mut temp = identity_matrix(); + if values.skew[2] != 0.0 { + temp[1][2] = values.skew[2]; + matrix = multiply_matrices(matrix, temp); + } + if values.skew[1] != 0.0 { + temp[1][2] = 0.0; + temp[0][2] = values.skew[1]; + matrix = multiply_matrices(matrix, temp); + } + if values.skew[0] != 0.0 { + temp[0][2] = 0.0; + temp[0][1] = values.skew[0]; + matrix = multiply_matrices(matrix, temp); + } + + // apply scale + for index in 0..3 { + for row in &mut matrix { + row[index] *= values.scale[index]; + } + } + matrix +} + +// https://drafts.csswg.org/css-transforms-2/#interpolation-of-decomposed-3d-matrix-values +fn slerp_quaternions(from: [f64; 4], to: [f64; 4], delta: f32) -> [f64; 4] { + let product = from + .iter() + .zip(to) + .map(|(from, to)| from * to) + .sum::() + .clamp(-1.0, 1.0); + if product.abs() >= 1.0 { + return from; + } + let theta = product.acos(); + let weight = (f64::from(delta) * theta).sin() / (1.0 - product * product).sqrt(); + let from_multiplier = (f64::from(delta) * theta).cos() - product * weight; + if weight.abs() < f64::from(f32::EPSILON) { + return from.map(|component| component * from_multiplier); + } + if from_multiplier.abs() < f64::from(f32::EPSILON) { + return to.map(|component| component * weight); + } + std::array::from_fn(|index| from[index] * from_multiplier + to[index] * weight) +} + +fn interpolate_matrices(from: Matrix4, to: Matrix4, delta: f32) -> Option { + let from = decompose_matrix(from)?; + let to = decompose_matrix(to)?; + let interpolate_array = |from: [f64; 3], to: [f64; 3]| { + std::array::from_fn(|index| interpolate_f64(from[index], to[index], delta, None)) + }; + let perspective = + std::array::from_fn(|index| interpolate_f64(from.perspective[index], to.perspective[index], delta, None)); + Some(recompose_matrix(DecomposedMatrix { + translation: interpolate_array(from.translation, to.translation), + scale: interpolate_array(from.scale, to.scale), + skew: interpolate_array(from.skew, to.skew), + rotation: slerp_quaternions(from.rotation, to.rotation, delta), + perspective, + })) +} + +fn transformation_to_matrix( + context: Option<&FfiAnimationContext>, + function: u8, + arguments: &[RetainedStyleValueData], +) -> Option { + let number = |argument: &RetainedStyleValueData| match argument.data() { + StyleValueData::Number { value } => Some(*value), + StyleValueData::Percentage { value } => Some(*value / 100.0), + _ => None, + }; + let length = |argument: &RetainedStyleValueData, reference_length: Option| match argument.data() { + StyleValueData::Length { value, unit } => crate::style_compute::absolute_length_to_px(*value, *unit), + StyleValueData::Percentage { value } => { + reference_length.map(|reference_length| value / 100.0 * reference_length) + } + _ => None, + }; + let angle = |argument: &RetainedStyleValueData| match argument.data() { + StyleValueData::Angle { value, unit } => angle_to_degrees(*value, *unit).map(f64::to_radians), + _ => None, + }; + let numbers = || arguments.iter().map(number).collect::>>(); + let reference_box = context + .filter(|context| context.has_transform_reference_box) + .map(|context| { + ( + context.transform_reference_box_width, + context.transform_reference_box_height, + ) + }); + let translation_matrix = |x: f64, y: f64, z: f64| { + let mut matrix = identity_matrix(); + matrix[0][3] = x; + matrix[1][3] = y; + matrix[2][3] = z; + matrix + }; + let scale_matrix = |x: f64, y: f64, z: f64| { + let mut matrix = identity_matrix(); + matrix[0][0] = x; + matrix[1][1] = y; + matrix[2][2] = z; + matrix + }; + let rotation_matrix = |axis: [f64; 3], angle: f64| { + let axis_length = vector_length(axis); + if axis_length < 1e-5 { + return identity_matrix(); + } + let [x, y, z] = axis.map(|component| component / axis_length); + let cosine = angle.cos(); + let sine = angle.sin(); + let one_minus_cosine = 1.0 - cosine; + [ + [ + cosine + x * x * one_minus_cosine, + x * y * one_minus_cosine - z * sine, + x * z * one_minus_cosine + y * sine, + 0.0, + ], + [ + y * x * one_minus_cosine + z * sine, + cosine + y * y * one_minus_cosine, + y * z * one_minus_cosine - x * sine, + 0.0, + ], + [ + z * x * one_minus_cosine - y * sine, + z * y * one_minus_cosine + x * sine, + cosine + z * z * one_minus_cosine, + 0.0, + ], + [0.0, 0.0, 0.0, 1.0], + ] + }; + + match (function, arguments) { + (TRANSFORM_FUNCTION_MATRIX, [a, b, c, d, e, f]) => Some([ + [number(a)?, number(c)?, 0.0, number(e)?], + [number(b)?, number(d)?, 0.0, number(f)?], + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + ]), + (TRANSFORM_FUNCTION_MATRIX_3D, values) if values.len() == 16 => { + let values = numbers()?; + Some(std::array::from_fn(|row| { + std::array::from_fn(|column| values[column * 4 + row]) + })) + } + (TRANSFORM_FUNCTION_PERSPECTIVE, [argument]) => match argument.data() { + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword() => { + Some(identity_matrix()) + } + _ => { + let depth = length(argument, None)?.max(1.0); + let mut matrix = identity_matrix(); + matrix[3][2] = -1.0 / depth; + Some(matrix) + } + }, + (TRANSFORM_FUNCTION_TRANSLATE | TRANSFORM_FUNCTION_TRANSLATE_X, [x]) => Some(translation_matrix( + length(x, reference_box.map(|(width, _)| width))?, + 0.0, + 0.0, + )), + (TRANSFORM_FUNCTION_TRANSLATE, [x, y]) => Some(translation_matrix( + length(x, reference_box.map(|(width, _)| width))?, + length(y, reference_box.map(|(_, height)| height))?, + 0.0, + )), + (TRANSFORM_FUNCTION_TRANSLATE_Y, [y]) => Some(translation_matrix( + 0.0, + length(y, reference_box.map(|(_, height)| height))?, + 0.0, + )), + (TRANSFORM_FUNCTION_TRANSLATE_Z, [z]) => Some(translation_matrix(0.0, 0.0, length(z, None)?)), + (TRANSFORM_FUNCTION_TRANSLATE_3D, [x, y, z]) => Some(translation_matrix( + length(x, reference_box.map(|(width, _)| width))?, + length(y, reference_box.map(|(_, height)| height))?, + length(z, None)?, + )), + (TRANSFORM_FUNCTION_SCALE, [value]) => { + let value = number(value)?; + Some(scale_matrix(value, value, 1.0)) + } + (TRANSFORM_FUNCTION_SCALE, [x, y]) => Some(scale_matrix(number(x)?, number(y)?, 1.0)), + (TRANSFORM_FUNCTION_SCALE_X, [x]) => Some(scale_matrix(number(x)?, 1.0, 1.0)), + (TRANSFORM_FUNCTION_SCALE_Y, [y]) => Some(scale_matrix(1.0, number(y)?, 1.0)), + (TRANSFORM_FUNCTION_SCALE_Z, [z]) => Some(scale_matrix(1.0, 1.0, number(z)?)), + (TRANSFORM_FUNCTION_SCALE_3D, [x, y, z]) => Some(scale_matrix(number(x)?, number(y)?, number(z)?)), + (TRANSFORM_FUNCTION_ROTATE | TRANSFORM_FUNCTION_ROTATE_Z, [value]) => { + Some(rotation_matrix([0.0, 0.0, 1.0], angle(value)?)) + } + (TRANSFORM_FUNCTION_ROTATE_X, [value]) => Some(rotation_matrix([1.0, 0.0, 0.0], angle(value)?)), + (TRANSFORM_FUNCTION_ROTATE_Y, [value]) => Some(rotation_matrix([0.0, 1.0, 0.0], angle(value)?)), + (TRANSFORM_FUNCTION_ROTATE_3D, [x, y, z, value]) => { + Some(rotation_matrix([number(x)?, number(y)?, number(z)?], angle(value)?)) + } + (TRANSFORM_FUNCTION_SKEW | TRANSFORM_FUNCTION_SKEW_X, [x]) => { + let mut matrix = identity_matrix(); + matrix[0][1] = angle(x)?.tan(); + Some(matrix) + } + (TRANSFORM_FUNCTION_SKEW, [x, y]) => { + let mut matrix = identity_matrix(); + matrix[0][1] = angle(x)?.tan(); + matrix[1][0] = angle(y)?.tan(); + Some(matrix) + } + (TRANSFORM_FUNCTION_SKEW_Y, [y]) => { + let mut matrix = identity_matrix(); + matrix[1][0] = angle(y)?.tan(); + Some(matrix) + } + _ => None, + } +} + +fn matrix_transformation(property: u16, matrix: Matrix4) -> StyleValueData { + let arguments = (0..16) + .map(|index| retained_number(matrix[index % 4][index / 4])) + .collect(); + StyleValueData::Transformation { + property, + transform_function: TRANSFORM_FUNCTION_MATRIX_3D, + values: RetainedStyleValueDataList::from_retained_values(arguments), + } +} + +enum TransformMatrixInterpolationError { + NotConvertible, + NonInvertible, +} + +fn interpolate_transform_matrix_suffix( + context: Option<&FfiAnimationContext>, + property: u16, + from: &[RetainedStyleValueData], + to: &[RetainedStyleValueData], + delta: f32, +) -> Result { + let post_multiply = |transformations: &[RetainedStyleValueData]| { + let mut result = identity_matrix(); + for transformation in transformations { + let StyleValueData::Transformation { + transform_function, + values, + .. + } = transformation.data() + else { + return None; + }; + result = multiply_matrices( + result, + transformation_to_matrix(context, *transform_function, values.as_slice())?, + ); + } + Some(result) + }; + let from = post_multiply(from).ok_or(TransformMatrixInterpolationError::NotConvertible)?; + let to = post_multiply(to).ok_or(TransformMatrixInterpolationError::NotConvertible)?; + let matrix = interpolate_matrices(from, to, delta).ok_or(TransformMatrixInterpolationError::NonInvertible)?; + let transformation = Arc::into_raw(Arc::new(matrix_transformation(property, matrix))); + Ok(unsafe { RetainedStyleValueData::from_retained_pointer(transformation) }) +} + +fn interpolate_transform_list( + context: Option<&FfiAnimationContext>, + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> Option> { + if matches!(from, StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword()) + && matches!(to, StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword()) + { + // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms + // * If both Va and Vb are none: + // * Vresult is none. + return Some(Some(StyleValueData::Keyword { + keyword: crate::style_compute::none_keyword(), + })); + } + + let decode_transform_list = |value: &StyleValueData| match value { + StyleValueData::ValueList { + values, + separator, + collapsible, + } => Some(( + values + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect::>(), + *separator, + *collapsible, + )), + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword() => { + Some((Vec::new(), 0, false)) + } + _ => None, + }; + let (mut from_values, from_separator, from_collapsible) = decode_transform_list(from)?; + let (mut to_values, to_separator, to_collapsible) = decode_transform_list(to)?; + let (separator, collapsible) = if from_values.is_empty() { + (to_separator, to_collapsible) + } else { + (from_separator, from_collapsible) + }; + if !from_values.is_empty() + && !to_values.is_empty() + && (from_separator != to_separator || from_collapsible != to_collapsible) + { + return None; + } + // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms + // * Treating none as a list of zero length, if Va or Vb differ in length: + // * extend the shorter list to the length of the longer list, setting the function at each additional + // position to the identity transform function matching the function at the corresponding position in the + // longer list. Both transform function lists are then interpolated following the next rule. + if from_values.len() != to_values.len() { + let (shorter, longer) = if from_values.len() < to_values.len() { + (&mut from_values, &to_values) + } else { + (&mut to_values, &from_values) + }; + for transformation in &longer[shorter.len()..] { + let StyleValueData::Transformation { + property, + transform_function, + .. + } = transformation.data() + else { + return None; + }; + shorter.push(identity_transformation(*property, *transform_function)?); + } + } + + // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms + // * Let Vresult be an empty list. Beginning at the start of Va and Vb, compare the corresponding functions at each + // position: + // * While the functions have either the same name, or are derivatives of the same primitive transform + // function, interpolate the corresponding pair of functions as described in § 10 Interpolation of + // primitives and derived transform functions and append the result to Vresult. + let mut transformations = Vec::with_capacity(from_values.len()); + for (index, (from, to)) in from_values.iter().zip(&to_values).enumerate() { + let ( + StyleValueData::Transformation { + property: from_property, + transform_function: from_function, + values: from_arguments, + }, + StyleValueData::Transformation { + transform_function: to_function, + values: to_arguments, + .. + }, + ) = (from.data(), to.data()) + else { + return None; + }; + if from_function == to_function + && *from_function == TRANSFORM_FUNCTION_PERSPECTIVE + && index + 1 == from_values.len() + { + let ([from_argument], [to_argument]) = (from_arguments.as_slice(), to_arguments.as_slice()) else { + return None; + }; + let reciprocal_depth = |argument: &RetainedStyleValueData| match argument.data() { + StyleValueData::Length { value, unit } => Some((1.0 / value.max(1.0), Some(*unit))), + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword() => { + Some((0.0, None)) + } + _ => None, + }; + let (Some((from_reciprocal_depth, from_unit)), Some((to_reciprocal_depth, to_unit))) = + (reciprocal_depth(from_argument), reciprocal_depth(to_argument)) + else { + return None; + }; + if from_unit.is_some() && to_unit.is_some() && from_unit != to_unit { + return None; + } + + // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions + // The transform functions , matrix3d() and perspective() get converted into 4x4 matrices first and + // interpolated as defined in section Interpolation of Matrices afterwards. + // OPTIMIZATION: A perspective matrix's only varying component is the negative reciprocal of its depth, so + // interpolating that component and inverting it produces the same result without materializing + // and decomposing two matrices. + let reciprocal_depth = interpolate_f64(from_reciprocal_depth, to_reciprocal_depth, delta, None); + let argument = if reciprocal_depth == 0.0 { + retained_none_keyword() + } else { + let value = Arc::into_raw(Arc::new(StyleValueData::Length { + value: 1.0 / reciprocal_depth, + unit: from_unit + .or(to_unit) + .unwrap_or_else(crate::style_compute::px_length_unit), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } + }; + let transformation = Arc::into_raw(Arc::new(StyleValueData::Transformation { + property: *from_property, + transform_function: *from_function, + values: RetainedStyleValueDataList::from_retained_values(vec![argument]), + })); + transformations.push(unsafe { RetainedStyleValueData::from_retained_pointer(transformation) }); + continue; + } + let Some((transform_function, from_arguments, to_arguments)) = + convert_transform_pair_to_common_primitive(*from_function, from_arguments, *to_function, to_arguments) + else { + // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms + // * If the pair do not have a common name or primitive transform function, post-multiply the remaining + // transform functions in each of Va and Vb respectively to produce two 4x4 matrices. Interpolate these two + // matrices as described in § 11 Interpolation of Matrices, append the result to Vresult, and cease + // iterating over Va and Vb. + let transformation = interpolate_transform_matrix_suffix( + context, + *from_property, + &from_values[index..], + &to_values[index..], + delta, + ); + match transformation { + Ok(transformation) => transformations.push(transformation), + Err(TransformMatrixInterpolationError::NotConvertible) => return None, + Err(TransformMatrixInterpolationError::NonInvertible) => { + // https://drafts.csswg.org/css-transforms-1/#interpolation-of-transforms + // In some cases, an animation might cause a transformation matrix to be singular or non-invertible. + // For example, an animation in which scale moves from 1 to -1. At the time when the matrix is in + // such a state, the transformed element is not rendered. + // If one of the matrices for interpolation is non-invertible, the used animation function must + // fall-back to a discrete animation according to the rules of the respective animation specification. + return Some(None); + } + } + break; + }; + + // https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions + // Two different types of transform functions that share the same primitive, or transform functions of the same + // type with different number of arguments can be interpolated. Both transform functions need a former + // conversion to the common primitive first and get interpolated numerically afterwards. The computed value will + // be the primitive with the resulting interpolated arguments. + if transform_function == TRANSFORM_FUNCTION_ROTATE_3D { + let from_arguments = RetainedStyleValueDataList::from_retained_values( + from_arguments + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + ); + let to_arguments = RetainedStyleValueDataList::from_retained_values( + to_arguments + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + ); + let transformation = interpolate_rotate_3d( + *from_property, + transform_function, + &from_arguments, + &to_arguments, + delta, + )?; + let transformation = Arc::into_raw(Arc::new(transformation)); + transformations.push(unsafe { RetainedStyleValueData::from_retained_pointer(transformation) }); + continue; + } + + let mut arguments = Vec::with_capacity(from_arguments.len()); + for (from, to) in from_arguments.iter().zip(to_arguments) { + if matches!( + transform_function, + TRANSFORM_FUNCTION_TRANSLATE | TRANSFORM_FUNCTION_TRANSLATE_3D + ) { + arguments.push(interpolate_translate_component( + property_id, + from.data(), + to.data(), + delta, + )?); + continue; + } + let result = interpolate_scalar_value(property_id, from.data(), to.data(), delta, &[]); + if !result.handled || result.value.is_null() { + return None; + }; + arguments.push(unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }); + } + let transformation = Arc::into_raw(Arc::new(StyleValueData::Transformation { + property: *from_property, + transform_function, + values: RetainedStyleValueDataList::from_retained_values(arguments), + })); + transformations.push(unsafe { RetainedStyleValueData::from_retained_pointer(transformation) }); + } + + Some(Some(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(transformations), + separator, + collapsible, + })) +} + +fn retained_legacy_color(red: u8, green: u8, blue: u8, alpha: u8) -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::ColorFunction { + color_base: ColorBase { + has_color_type: true, + color_type: COLOR_TYPE_RGB, + color_syntax: COLOR_SYNTAX_LEGACY, + }, + channel_0: retained_number(red as f64), + channel_1: retained_number(green as f64), + channel_2: retained_number(blue as f64), + alpha: retained_number(alpha as f64 / 255.0), + has_name: false, + name: empty_retained_fly_string(), + origin_color: empty_retained_style_value(), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn retained_transparent_legacy_color() -> RetainedStyleValueData { + retained_legacy_color(0, 0, 0, 0) +} + +fn animation_length_resolution_context( + context: Option<&FfiAnimationContext>, +) -> Option { + let animation_context = &context + .filter(|context| context.has_length_resolution_context)? + .length_resolution_context; + let font_metrics = |metrics: &FfiAnimationFontMetrics| crate::style_compute::FfiFontMetrics { + font_size: metrics.font_size, + x_height: metrics.x_height, + cap_height: metrics.cap_height, + zero_advance: metrics.zero_advance, + line_height: metrics.line_height, + }; + Some(crate::style_compute::FfiLengthResolutionContext { + viewport_width: animation_context.viewport_width, + viewport_height: animation_context.viewport_height, + font_metrics: font_metrics(&animation_context.font_metrics), + root_font_metrics: font_metrics(&animation_context.root_font_metrics), + font_metrics_depend_on_viewport_metrics: animation_context.font_metrics_depend_on_viewport_metrics, + root_font_metrics_depend_on_viewport_metrics: animation_context.root_font_metrics_depend_on_viewport_metrics, + }) +} + +fn resolve_animation_length(context: Option<&FfiAnimationContext>, value: &StyleValueData) -> Option { + match value { + StyleValueData::Length { value, unit } => crate::style_compute::absolute_length_to_px(*value, *unit), + StyleValueData::Calculated { .. } => { + crate::calc::resolve_calculated_length_with_context(value, &animation_length_resolution_context(context)?) + } + _ => None, + } +} + +fn interpolate_animation_length( + context: Option<&FfiAnimationContext>, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, + range: Option<(f64, f64)>, +) -> Option { + Some(retained_length( + interpolate_f64( + resolve_animation_length(context, from)?, + resolve_animation_length(context, to)?, + delta, + range, + ), + crate::style_compute::px_length_unit(), + )) +} + +fn resolve_animation_angle(context: Option<&FfiAnimationContext>, value: &StyleValueData) -> Option { + match value { + StyleValueData::Angle { value, unit } => angle_to_degrees(*value, *unit), + StyleValueData::Calculated { .. } => animation_length_resolution_context(context) + .and_then(|context| crate::calc::resolve_calculated_angle_with_context(value, &context)) + .or_else(|| crate::calc::resolve_calculated_angle_without_context(value)), + _ => None, + } +} + +fn resolve_animation_number(context: Option<&FfiAnimationContext>, value: &StyleValueData) -> Option { + match value { + StyleValueData::Number { value } => Some(*value), + StyleValueData::Calculated { .. } => animation_length_resolution_context(context) + .and_then(|context| crate::calc::resolve_calculated_number_with_context(value, &context)) + .or_else(|| crate::calc::resolve_calculated_number_without_context(value)), + _ => None, + } +} + +fn resolve_animation_color( + context: Option<&FfiAnimationContext>, + color: &RetainedStyleValueData, +) -> Option { + let use_current_color = match color.optional_data() { + None => true, + Some(StyleValueData::Keyword { keyword }) => *keyword == crate::style_compute::current_color_keyword(), + Some(_) => false, + }; + if !use_current_color { + return Some(color.clone_retained()); + } + let current_color = context?.current_color; + if current_color.is_null() { + return None; + } + let retained = unsafe { crate::style_value::rust_style_value_retain(current_color) }; + Some(unsafe { RetainedStyleValueData::from_retained_pointer(retained) }) +} + +enum RootAnimationColor<'a> { + Borrowed(&'a StyleValueData), + Owned(StyleValueData), +} + +impl RootAnimationColor<'_> { + fn data(&self) -> &StyleValueData { + match self { + Self::Borrowed(value) => value, + Self::Owned(value) => value, + } + } +} + +fn resolve_root_animation_color<'a>( + context: Option<&'a FfiAnimationContext>, + color: &'a StyleValueData, +) -> Option> { + match color { + StyleValueData::ColorFunction { .. } => Some(RootAnimationColor::Borrowed(color)), + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::current_color_keyword() => { + let current_color = context?.current_color; + if current_color.is_null() { + return None; + } + let (components, missing) = legacy_srgb_components(unsafe { &*current_color })?; + if missing.iter().any(|component| *component) { + return None; + } + let to_byte = |value: f32| (value * 255.0).round().clamp(0.0, 255.0) as u8; + let alpha = to_byte(components[3]); + Some(RootAnimationColor::Owned(StyleValueData::ColorFunction { + color_base: ColorBase { + has_color_type: true, + color_type: COLOR_TYPE_RGB, + color_syntax: COLOR_SYNTAX_LEGACY, + }, + channel_0: retained_number(to_byte(components[0]) as f64), + channel_1: retained_number(to_byte(components[1]) as f64), + channel_2: retained_number(to_byte(components[2]) as f64), + alpha: retained_number(alpha as f64 / 255.0), + has_name: false, + name: empty_retained_fly_string(), + origin_color: empty_retained_style_value(), + })) + } + _ => None, + } +} + +fn blank_shadow(other: &StyleValueData) -> Option { + let StyleValueData::Shadow { + shadow_type, placement, .. + } = other + else { + return None; + }; + let value = Arc::into_raw(Arc::new(StyleValueData::Shadow { + shadow_type: *shadow_type, + color: retained_transparent_legacy_color(), + offset_x: retained_zero_px(), + offset_y: retained_zero_px(), + blur_radius: retained_zero_px(), + spread_distance: retained_zero_px(), + placement: *placement, + })); + Some(unsafe { RetainedStyleValueData::from_retained_pointer(value) }) +} + +fn interpolate_shadow( + context: Option<&FfiAnimationContext>, + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> Option { + let ( + StyleValueData::Shadow { + shadow_type: from_shadow_type, + color: from_color, + offset_x: from_offset_x, + offset_y: from_offset_y, + blur_radius: from_blur_radius, + spread_distance: from_spread_distance, + placement: from_placement, + }, + StyleValueData::Shadow { + shadow_type: to_shadow_type, + color: to_color, + offset_x: to_offset_x, + offset_y: to_offset_y, + blur_radius: to_blur_radius, + spread_distance: to_spread_distance, + placement: to_placement, + }, + ) = (from, to) + else { + return None; + }; + if from_shadow_type != to_shadow_type { + return None; + } + + let from_blur_default = retained_zero_px(); + let to_blur_default = retained_zero_px(); + let from_spread_default = retained_zero_px(); + let to_spread_default = retained_zero_px(); + let from_blur_radius = if from_blur_radius.optional_data().is_some() { + from_blur_radius + } else { + &from_blur_default + }; + let to_blur_radius = if to_blur_radius.optional_data().is_some() { + to_blur_radius + } else { + &to_blur_default + }; + let from_spread_distance = if from_spread_distance.optional_data().is_some() { + from_spread_distance + } else { + &from_spread_default + }; + let to_spread_distance = if to_spread_distance.optional_data().is_some() { + to_spread_distance + } else { + &to_spread_default + }; + let from_color = resolve_animation_color(context, from_color)?; + let to_color = resolve_animation_color(context, to_color)?; + + let interpolate = |from: &RetainedStyleValueData, to: &RetainedStyleValueData, ranges: &[NumericRangeOverride]| { + retained_animation_result(interpolate_scalar_value( + property_id, + from.data(), + to.data(), + delta, + ranges, + )) + }; + let color = interpolate(&from_color, &to_color, &[])?; + let offset_x = interpolate_animation_length(context, from_offset_x.data(), to_offset_x.data(), delta, None) + .or_else(|| interpolate(from_offset_x, to_offset_x, &[]))?; + let offset_y = interpolate_animation_length(context, from_offset_y.data(), to_offset_y.data(), delta, None) + .or_else(|| interpolate(from_offset_y, to_offset_y, &[]))?; + let blur_radius = interpolate_animation_length( + context, + from_blur_radius.data(), + to_blur_radius.data(), + delta, + Some((0.0, f64::INFINITY)), + ) + .or_else(|| interpolate(from_blur_radius, to_blur_radius, NONNEGATIVE_LENGTH_RANGE))?; + let spread_distance = interpolate_animation_length( + context, + from_spread_distance.data(), + to_spread_distance.data(), + delta, + None, + ) + .or_else(|| interpolate(from_spread_distance, to_spread_distance, &[]))?; + + Some(StyleValueData::Shadow { + shadow_type: *from_shadow_type, + color, + offset_x, + offset_y, + blur_radius, + spread_distance, + placement: if delta >= 0.5 { *to_placement } else { *from_placement }, + }) +} + +fn interpolate_shadow_list( + context: Option<&FfiAnimationContext>, + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + // https://drafts.csswg.org/css-backgrounds/#box-shadow + // Animation type: by computed value, treating none as a zero-item list and appending blank shadows + // (transparent 0 0 0 0) with a corresponding inset keyword as needed to match the longer list if + // the shorter list is otherwise compatible with the longer one + let list = |value: &StyleValueData| -> Option<(Vec, u8, bool)> { + match value { + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword() => { + Some((Vec::new(), 0, false)) + } + StyleValueData::ValueList { + values, + separator, + collapsible, + } => Some(( + values + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + *separator, + *collapsible, + )), + _ => None, + } + }; + let Some((mut from_shadows, from_separator, from_collapsible)) = list(from) else { + return not_handled(); + }; + let Some((mut to_shadows, to_separator, to_collapsible)) = list(to) else { + return not_handled(); + }; + let from_was_empty = from_shadows.is_empty(); + + while from_shadows.len() < to_shadows.len() { + let Some(shadow) = blank_shadow(to_shadows[from_shadows.len()].data()) else { + return not_handled(); + }; + from_shadows.push(shadow); + } + while to_shadows.len() < from_shadows.len() { + let Some(shadow) = blank_shadow(from_shadows[to_shadows.len()].data()) else { + return not_handled(); + }; + to_shadows.push(shadow); + } + + let mut shadows = Vec::with_capacity(from_shadows.len()); + for (from_shadow, to_shadow) in from_shadows.iter().zip(&to_shadows) { + let Some(shadow) = interpolate_shadow(context, property_id, from_shadow.data(), to_shadow.data(), delta) else { + return discrete_value(context, from, to, delta); + }; + let shadow = Arc::into_raw(Arc::new(shadow)); + shadows.push(unsafe { RetainedStyleValueData::from_retained_pointer(shadow) }); + } + + let use_to_metadata = from_was_empty && !to_shadows.is_empty(); + owned(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(shadows), + separator: if use_to_metadata { to_separator } else { from_separator }, + collapsible: if use_to_metadata { + to_collapsible + } else { + from_collapsible + }, + }) +} + +const FILTER_KIND_BLUR: u8 = 0; +const FILTER_KIND_DROP_SHADOW: u8 = 1; +const FILTER_KIND_HUE_ROTATE: u8 = 2; +const FILTER_KIND_COLOR: u8 = 3; + +fn retained_filter(kind: u8, color_operation: u8, value: RetainedStyleValueData) -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Filter { + kind, + color_operation, + value, + })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn initial_filter_value( + value: &StyleValueData, + use_transparent_drop_shadow_color: bool, +) -> Option { + let StyleValueData::Filter { + kind, + color_operation, + value, + } = value + else { + return None; + }; + let initial = match *kind { + FILTER_KIND_BLUR => retained_zero_px(), + FILTER_KIND_DROP_SHADOW => { + let StyleValueData::Shadow { + shadow_type, placement, .. + } = value.data() + else { + return None; + }; + let shadow = Arc::into_raw(Arc::new(StyleValueData::Shadow { + shadow_type: *shadow_type, + color: if use_transparent_drop_shadow_color { + retained_transparent_legacy_color() + } else { + empty_retained_style_value() + }, + offset_x: retained_zero_px(), + offset_y: retained_zero_px(), + blur_radius: retained_zero_px(), + spread_distance: empty_retained_style_value(), + placement: *placement, + })); + unsafe { RetainedStyleValueData::from_retained_pointer(shadow) } + } + FILTER_KIND_HUE_ROTATE => { + let angle = Arc::into_raw(Arc::new(StyleValueData::Angle { value: 0.0, unit: 0 })); + unsafe { RetainedStyleValueData::from_retained_pointer(angle) } + } + FILTER_KIND_COLOR => retained_number(if matches!(*color_operation, 2 | 3 | 6) { + 0.0 + } else { + 1.0 + }), + _ => return None, + }; + Some(retained_filter(*kind, *color_operation, initial)) +} + +// https://drafts.fxtf.org/filter-effects/#interpolation-of-filter-functions +fn interpolate_filter_function( + context: Option<&FfiAnimationContext>, + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> Option { + let ( + StyleValueData::Filter { + kind: from_kind, + color_operation: from_color_operation, + value: from_value, + }, + StyleValueData::Filter { + kind: to_kind, + color_operation: to_color_operation, + value: to_value, + }, + ) = (from, to) + else { + return None; + }; + if from_kind != to_kind { + return None; + } + + let value = match *from_kind { + FILTER_KIND_BLUR => interpolate_animation_length( + context, + from_value.data(), + to_value.data(), + delta, + Some((0.0, f32::MAX as f64)), + ) + .or_else(|| { + retained_animation_result(interpolate_scalar_value( + property_id, + from_value.data(), + to_value.data(), + delta, + NONNEGATIVE_LENGTH_RANGE, + )) + })?, + FILTER_KIND_DROP_SHADOW => { + let mut shadow = interpolate_shadow(context, property_id, from_value.data(), to_value.data(), delta)?; + let ( + StyleValueData::Shadow { + blur_radius: from_blur_radius, + .. + }, + StyleValueData::Shadow { + blur_radius: to_blur_radius, + .. + }, + StyleValueData::Shadow { + blur_radius, + spread_distance, + .. + }, + ) = (from_value.data(), to_value.data(), &mut shadow) + else { + return None; + }; + let selected_blur_radius = if delta >= 0.5 { to_blur_radius } else { from_blur_radius }; + if selected_blur_radius.optional_data().is_none() { + *blur_radius = empty_retained_style_value(); + } + *spread_distance = empty_retained_style_value(); + let shadow = Arc::into_raw(Arc::new(shadow)); + unsafe { RetainedStyleValueData::from_retained_pointer(shadow) } + } + FILTER_KIND_HUE_ROTATE => { + let from = resolve_animation_angle(context, from_value.data())?; + let to = resolve_animation_angle(context, to_value.data())?; + let angle = Arc::into_raw(Arc::new(StyleValueData::Angle { + value: interpolate_f64(from, to, delta, None), + unit: 0, + })); + unsafe { RetainedStyleValueData::from_retained_pointer(angle) } + } + FILTER_KIND_COLOR if from_color_operation == to_color_operation => { + let ranges = if matches!(*from_color_operation, 2 | 3 | 4 | 6) { + &[NumericRangeOverride { + value_type: VALUE_TYPE_NUMBER, + min: 0.0, + max: 1.0, + }][..] + } else { + &[NumericRangeOverride { + value_type: VALUE_TYPE_NUMBER, + min: 0.0, + max: f32::MAX as f64, + }][..] + }; + retained_animation_result(interpolate_scalar_value( + property_id, + from_value.data(), + to_value.data(), + delta, + ranges, + ))? + } + _ => return None, + }; + Some(retained_filter(*from_kind, *from_color_operation, value)) +} + +fn accumulate_filter_function( + context: &FfiAnimationContext, + underlying: &StyleValueData, + animated: &StyleValueData, +) -> Option { + let ( + StyleValueData::Filter { + kind: underlying_kind, + color_operation: underlying_color_operation, + value: underlying_value, + }, + StyleValueData::Filter { + kind: animated_kind, + color_operation: animated_color_operation, + value: animated_value, + }, + ) = (underlying, animated) + else { + return None; + }; + if underlying_kind != animated_kind { + return None; + } + + let value = match *underlying_kind { + FILTER_KIND_BLUR => retained_length( + resolve_animation_length(Some(context), underlying_value.data())? + + resolve_animation_length(Some(context), animated_value.data())?, + crate::style_compute::px_length_unit(), + ), + FILTER_KIND_HUE_ROTATE => { + let angle = Arc::into_raw(Arc::new(StyleValueData::Angle { + value: resolve_animation_angle(Some(context), underlying_value.data())? + + resolve_animation_angle(Some(context), animated_value.data())?, + unit: 0, + })); + unsafe { RetainedStyleValueData::from_retained_pointer(angle) } + } + FILTER_KIND_COLOR if underlying_color_operation == animated_color_operation => { + let underlying = resolve_animation_number(Some(context), underlying_value.data())?; + let animated = resolve_animation_number(Some(context), animated_value.data())?; + retained_number(if matches!(*underlying_color_operation, 0 | 1 | 4 | 5) { + underlying + animated - 1.0 + } else { + underlying + animated + }) + } + FILTER_KIND_DROP_SHADOW => { + let ( + StyleValueData::Shadow { + shadow_type, + color: underlying_color, + offset_x: underlying_offset_x, + offset_y: underlying_offset_y, + blur_radius: underlying_blur_radius, + placement, + .. + }, + StyleValueData::Shadow { + color: animated_color, + offset_x: animated_offset_x, + offset_y: animated_offset_y, + blur_radius: animated_blur_radius, + .. + }, + ) = (underlying_value.data(), animated_value.data()) + else { + return None; + }; + let add_lengths = |underlying: &RetainedStyleValueData, animated: &RetainedStyleValueData| { + Some(retained_length( + resolve_animation_length(Some(context), underlying.data())? + + resolve_animation_length(Some(context), animated.data())?, + crate::style_compute::px_length_unit(), + )) + }; + let offset_x = add_lengths(underlying_offset_x, animated_offset_x)?; + let offset_y = add_lengths(underlying_offset_y, animated_offset_y)?; + let blur_radius = + if underlying_blur_radius.optional_data().is_some() || animated_blur_radius.optional_data().is_some() { + let underlying = match underlying_blur_radius.optional_data() { + Some(value) => resolve_animation_length(Some(context), value)?, + None => 0.0, + }; + let animated = match animated_blur_radius.optional_data() { + Some(value) => resolve_animation_length(Some(context), value)?, + None => 0.0, + }; + retained_length(underlying + animated, crate::style_compute::px_length_unit()) + } else { + empty_retained_style_value() + }; + let color_bytes = |color: &RetainedStyleValueData| { + let color = resolve_animation_color(Some(context), color)?; + let (components, _) = legacy_srgb_components(color.data())?; + let byte = |value: f32| (value * 255.0).round().clamp(0.0, 255.0) as u8; + Some([ + byte(components[0]), + byte(components[1]), + byte(components[2]), + byte(components[3]), + ]) + }; + let underlying_color = color_bytes(underlying_color)?; + let animated_color = color_bytes(animated_color)?; + let add_color_component = + |underlying: u8, animated: u8| u16::from(underlying).saturating_add(u16::from(animated)).min(255) as u8; + let color = retained_legacy_color( + add_color_component(underlying_color[0], animated_color[0]), + add_color_component(underlying_color[1], animated_color[1]), + add_color_component(underlying_color[2], animated_color[2]), + add_color_component(underlying_color[3], animated_color[3]), + ); + let shadow = Arc::into_raw(Arc::new(StyleValueData::Shadow { + shadow_type: *shadow_type, + color, + offset_x, + offset_y, + blur_radius, + spread_distance: empty_retained_style_value(), + placement: *placement, + })); + unsafe { RetainedStyleValueData::from_retained_pointer(shadow) } + } + _ => return None, + }; + Some(retained_filter(*underlying_kind, *underlying_color_operation, value)) +} + +fn composite_filter_list( + context: &FfiAnimationContext, + underlying: &StyleValueData, + animated: &StyleValueData, + operation: FfiCompositeOperation, +) -> FfiAnimationValueResult { + if matches!(operation, FfiCompositeOperation::Replace) { + return handled_without_value(); + } + let list = |value: &StyleValueData| -> Option<(Vec, u8, bool)> { + match value { + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword() => { + Some((Vec::new(), 0, false)) + } + StyleValueData::ValueList { + values, + separator, + collapsible, + } => Some(( + values + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + *separator, + *collapsible, + )), + _ => None, + } + }; + let Some((mut underlying_filters, underlying_separator, underlying_collapsible)) = list(underlying) else { + return handled_without_value(); + }; + let Some((mut animated_filters, animated_separator, animated_collapsible)) = list(animated) else { + return handled_without_value(); + }; + let underlying_was_empty = underlying_filters.is_empty(); + + // https://drafts.fxtf.org/filter-effects/#addition + // Given two filter values representing an base value (base filter list) and a value to add (added filter list), + // returns the concatenation of the the two lists: ‘base filter list added filter list’. + if matches!(operation, FfiCompositeOperation::Add) { + if underlying_filters.is_empty() && animated_filters.is_empty() { + return handled_without_value(); + } + let use_animated_metadata = underlying_was_empty; + underlying_filters.append(&mut animated_filters); + return owned(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(underlying_filters), + separator: if use_animated_metadata { + animated_separator + } else { + underlying_separator + }, + collapsible: if use_animated_metadata { + animated_collapsible + } else { + underlying_collapsible + }, + }); + } + + // https://drafts.fxtf.org/filter-effects/#accumulation + // Accumulation of s follows the same matching and extending rules as interpolation, falling + // back to replace behavior if the lists do not match. However instead of interpolating the matching + // pairs, their arguments are arithmetically added together - except in the case of + // s whose initial value for interpolation is 1, which combine using one-based addition: + // Vresult = Va + Vb - 1 + if !underlying_filters + .iter() + .chain(&animated_filters) + .all(|value| matches!(value.data(), StyleValueData::Filter { .. })) + { + return handled_without_value(); + } + while underlying_filters.len() < animated_filters.len() { + let Some(value) = initial_filter_value(animated_filters[underlying_filters.len()].data(), false) else { + return handled_without_value(); + }; + underlying_filters.push(value); + } + while animated_filters.len() < underlying_filters.len() { + let Some(value) = initial_filter_value(underlying_filters[animated_filters.len()].data(), false) else { + return handled_without_value(); + }; + animated_filters.push(value); + } + if underlying_filters.is_empty() { + return handled_without_value(); + } + + let mut filters = Vec::with_capacity(underlying_filters.len()); + for (underlying, animated) in underlying_filters.iter().zip(&animated_filters) { + let Some(filter) = accumulate_filter_function(context, underlying.data(), animated.data()) else { + return handled_without_value(); + }; + filters.push(filter); + } + owned(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(filters), + separator: if underlying_was_empty { + animated_separator + } else { + underlying_separator + }, + collapsible: if underlying_was_empty { + animated_collapsible + } else { + underlying_collapsible + }, + }) +} + +fn interpolate_filter_list( + context: Option<&FfiAnimationContext>, + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + let list = |value: &StyleValueData| -> Option<(Vec, u8, bool)> { + match value { + StyleValueData::Keyword { keyword } if *keyword == crate::style_compute::none_keyword() => { + Some((Vec::new(), 0, false)) + } + StyleValueData::ValueList { + values, + separator, + collapsible, + } if values + .as_slice() + .iter() + .all(|value| matches!(value.data(), StyleValueData::Filter { .. })) => + { + Some(( + values + .as_slice() + .iter() + .map(RetainedStyleValueData::clone_retained) + .collect(), + *separator, + *collapsible, + )) + } + _ => None, + } + }; + let Some((mut from_filters, from_separator, from_collapsible)) = list(from) else { + return discrete_value(context, from, to, delta); + }; + let Some((mut to_filters, to_separator, to_collapsible)) = list(to) else { + return discrete_value(context, from, to, delta); + }; + if from_filters.is_empty() && to_filters.is_empty() { + return discrete_value(context, from, to, delta); + } + let from_was_empty = from_filters.is_empty(); + + // https://drafts.fxtf.org/filter-effects/#interpolation-of-filters + // If both filters have a of same length without and for each for which there is a corresponding item in each list + // Interpolate each pair following the rules in section Interpolation of Filter Functions. + + // If both filters have a of different length without and for each for which there is a corresponding item in each list + + // 1. Append the missing equivalent s from the longer list to the end of the shorter list. The new added s must be initialized to their initial values for interpolation. + while from_filters.len() < to_filters.len() { + let Some(value) = initial_filter_value(to_filters[from_filters.len()].data(), true) else { + return discrete_value(context, from, to, delta); + }; + from_filters.push(value); + } + while to_filters.len() < from_filters.len() { + let Some(value) = initial_filter_value(from_filters[to_filters.len()].data(), true) else { + return discrete_value(context, from, to, delta); + }; + to_filters.push(value); + } + + // If one filter is none and the other is a without + // + // 1. Replace none with the corresponding of the other filter. The new s must be initialized to their initial values for interpolation. + + // 2. Interpolate each pair following the rules in section Interpolation of Filter Functions. + let mut filters = Vec::with_capacity(from_filters.len()); + for (from_filter, to_filter) in from_filters.iter().zip(&to_filters) { + let Some(filter) = + interpolate_filter_function(context, property_id, from_filter.data(), to_filter.data(), delta) + else { + return discrete_value(context, from, to, delta); + }; + filters.push(filter); + } + + let use_to_metadata = from_was_empty && !to_filters.is_empty(); + owned(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(filters), + separator: if use_to_metadata { to_separator } else { from_separator }, + collapsible: if use_to_metadata { + to_collapsible + } else { + from_collapsible + }, + }) +} + +pub(crate) fn interpolate_value( + context: Option<&FfiAnimationContext>, + property_id: u16, + from: &StyleValueData, + to: &StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + let animation_type = property_animation_type(property_id); + if animation_type == ANIMATION_TYPE_NONE { + // https://www.w3.org/TR/web-animations-1/#not-animatable + // The property is not animatable. It is not processed when listed in an animation keyframe, and is not affected by transitions. + // NB: Such values are normally filtered before evaluation. Preserve the C++ scalar API's + // existing endpoint behavior if one reaches this lower-level operation. + return FfiAnimationValueResult { + value: unsafe { crate::style_value::rust_style_value_retain(to) }, + handled: true, + }; + } + if animation_type == ANIMATION_TYPE_DISCRETE { + // https://www.w3.org/TR/web-animations-1/#discrete + // The property’s values cannot be meaningfully combined, thus it is not additive and interpolation swaps from Va to Vb at 50% (p=0.5), i.e. + return discrete_value(context, from, to, delta); + } + if let (Some(resolved_from), Some(resolved_to)) = ( + resolve_root_animation_color(context, from), + resolve_root_animation_color(context, to), + ) { + // https://drafts.csswg.org/css-color-4/#interpolation + // Interpolating to or from currentcolor is possible. The numerical value used for this purpose is the used value. + let result = interpolate_scalar_value(property_id, resolved_from.data(), resolved_to.data(), delta, &[]); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM + && matches!( + property_id, + crate::property_metadata::property_id::FILTER | crate::property_metadata::property_id::BACKDROP_FILTER + ) + { + let result = interpolate_filter_list(context, property_id, from, to, delta); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM + && matches!( + property_id, + crate::property_metadata::property_id::BOX_SHADOW | crate::property_metadata::property_id::TEXT_SHADOW + ) + { + let result = interpolate_shadow_list(context, property_id, from, to, delta); + if result.handled { + return result; + } + } + let is_stroke_dasharray = animation_type == ANIMATION_TYPE_CUSTOM + && property_id == crate::property_metadata::property_id::STROKE_DASHARRAY; + if is_stroke_dasharray + && (!matches!(from, StyleValueData::ValueList { .. }) || !matches!(to, StyleValueData::ValueList { .. })) + { + // https://svgwg.org/svg2-draft/painting.html#StrokeDashing + // If either start or end compute to none or are invalid, start or end are combined using the discrete animation type. + return discrete_value(context, from, to, delta); + } + if animation_type == ANIMATION_TYPE_REPEATABLE_LIST || is_stroke_dasharray { + // https://svgwg.org/svg2-draft/painting.html#StrokeDashing + // Otherwise, repeat both dash patterns of start and end value list until the length of elements in + // both value lists match. Each item is then combined by computed value. + // https://drafts.csswg.org/web-animations-1/#repeatable-list + // Same as by computed value except that if the two lists have differing numbers of items, they are first repeated to the least common multiple number of items. + // Each item is then combined by computed value. + // If a pair of values cannot be combined or if any component value uses discrete animation, then the property values combine as discrete. + let from_list = match from { + StyleValueData::ValueList { + values, + separator, + collapsible, + } => Some((values.as_slice(), *separator, *collapsible)), + _ => None, + }; + let to_list = match to { + StyleValueData::ValueList { + values, + separator, + collapsible, + } => Some((values.as_slice(), *separator, *collapsible)), + _ => None, + }; + if from_list.is_none() && to_list.is_none() { + let result = interpolate_scalar_value(property_id, from, to, delta, &[]); + if result.handled && !result.value.is_null() { + return result; + } + return discrete_value(context, from, to, delta); + } + + let (separator, collapsible) = from_list + .map(|(_, separator, collapsible)| (separator, collapsible)) + .or_else(|| to_list.map(|(_, separator, collapsible)| (separator, collapsible))) + .expect("at least one repeatable value is a list"); + let from_length = from_list.map_or_else(|| to_list.unwrap().0.len(), |(values, _, _)| values.len()); + let to_length = to_list.map_or_else(|| from_list.unwrap().0.len(), |(values, _, _)| values.len()); + if from_length == 0 || to_length == 0 { + return owned(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(Vec::new()), + separator, + collapsible, + }); + } + + let mut a = from_length; + let mut b = to_length; + while b != 0 { + (a, b) = (b, a % b); + } + let Some(list_size) = (from_length / a).checked_mul(to_length) else { + return discrete_value(context, from, to, delta); + }; + + let mut values = Vec::with_capacity(list_size); + for index in 0..list_size { + let from_value = from_list.map_or(from, |(values, _, _)| values[index % from_length].data()); + let to_value = to_list.map_or(to, |(values, _, _)| values[index % to_length].data()); + let result = interpolate_scalar_value(property_id, from_value, to_value, delta, &[]); + if !result.handled { + return discrete_value(context, from, to, delta); + } + if result.value.is_null() { + return discrete_value(context, from, to, delta); + } + values.push(unsafe { RetainedStyleValueData::from_retained_pointer(result.value) }); + } + return owned(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(values), + separator, + collapsible, + }); + } + if animation_type == ANIMATION_TYPE_CUSTOM + && property_id == crate::property_metadata::property_id::FONT_STYLE + && let ( + StyleValueData::FontStyle { + font_style: from_font_style, + angle_value: from_angle, + }, + StyleValueData::FontStyle { + font_style: to_font_style, + angle_value: to_angle, + }, + ) = (from, to) + { + // https://drafts.csswg.org/css-fonts-4/#font-style-prop + // Animation type: by computed value type; normal animates as oblique 0deg + let normalize = |font_style: u8, angle: &RetainedStyleValueData| { + if font_style == FONT_STYLE_NORMAL { + return Some((FONT_STYLE_OBLIQUE, Some((0.0, 0)))); + } + match angle.optional_data() { + Some(StyleValueData::Angle { value, unit }) => Some((font_style, Some((*value, *unit)))), + Some(_) => None, + None => Some((font_style, None)), + } + }; + let (Some((from_font_style, from_angle)), Some((to_font_style, to_angle))) = ( + normalize(*from_font_style, from_angle), + normalize(*to_font_style, to_angle), + ) else { + return discrete_value(context, from, to, delta); + }; + let font_style = if from_font_style == to_font_style { + from_font_style + } else if !context.is_some_and(|context| context.allow_discrete) { + return handled_without_value(); + } else if delta < 0.5 { + from_font_style + } else { + to_font_style + }; + let angle_value = match (from_angle, to_angle) { + (Some((from_value, from_unit)), Some((to_value, to_unit))) => { + let (Some(from_value), Some(to_value)) = ( + angle_to_degrees(from_value, from_unit), + angle_to_degrees(to_value, to_unit), + ) else { + return discrete_value(context, from, to, delta); + }; + let angle = Arc::into_raw(Arc::new(StyleValueData::Angle { + value: interpolate_f64(from_value, to_value, delta, Some((-90.0, 90.0))), + unit: 0, + })); + unsafe { RetainedStyleValueData::from_retained_pointer(angle) } + } + _ => unsafe { RetainedStyleValueData::from_retained_optional_pointer(std::ptr::null()) }, + }; + return owned(StyleValueData::FontStyle { + font_style, + angle_value, + }); + } + if animation_type == ANIMATION_TYPE_CUSTOM && property_id == crate::property_metadata::property_id::VISIBILITY { + let result = interpolate_visibility(context, from, to, delta); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM + && property_id == crate::property_metadata::property_id::CONTENT_VISIBILITY + { + let result = interpolate_content_visibility(context, from, to, delta); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM && property_id == crate::property_metadata::property_id::DISPLAY { + let result = interpolate_display(context, from, to, delta); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM && property_id == crate::property_metadata::property_id::SCALE { + let result = interpolate_scale(from, to, delta); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM && property_id == crate::property_metadata::property_id::TRANSLATE { + let result = interpolate_translate(from, to, delta); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM && property_id == crate::property_metadata::property_id::ROTATE { + let result = interpolate_individual_rotate(from, to, delta); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM + && property_id == crate::property_metadata::property_id::FONT_VARIATION_SETTINGS + { + let result = interpolate_font_variation_settings(context, property_id, from, to, delta); + if result.handled { + return result; + } + } + if animation_type == ANIMATION_TYPE_CUSTOM + && property_id == crate::property_metadata::property_id::TRANSFORM + && let Some(value) = interpolate_transform_list(context, property_id, from, to, delta) + { + return value.map_or_else( + || { + if context.is_some_and(|context| context.allow_discrete) { + discrete_value(context, from, to, delta) + } else { + handled_without_value() + } + }, + owned, + ); + } + if animation_type == ANIMATION_TYPE_CUSTOM + && matches!( + property_id, + crate::property_metadata::property_id::GRID_TEMPLATE_COLUMNS + | crate::property_metadata::property_id::GRID_TEMPLATE_ROWS + ) + && let ( + StyleValueData::GridTrackSizeList { + is_subgrid: from_is_subgrid, + entries: from_entries, + .. + }, + StyleValueData::GridTrackSizeList { + is_subgrid: to_is_subgrid, + entries: to_entries, + .. + }, + ) = (from, to) + { + let Some(entries) = interpolate_grid_track_entries( + property_id, + *from_is_subgrid, + from_entries.as_slice(), + *to_is_subgrid, + to_entries.as_slice(), + delta, + ) else { + return discrete_value(context, from, to, delta); + }; + return owned(StyleValueData::GridTrackSizeList { + is_subgrid: false, + preserve_line_name_sets: false, + entries: RetainedGridTrackEntryList::from_retained_entries(entries), + }); + } + if animation_type == ANIMATION_TYPE_CUSTOM { + // NB: C++ treats values declined by a property's specialized custom algorithm as discrete. + return discrete_value(context, from, to, delta); + } + assert_eq!(animation_type, ANIMATION_TYPE_BY_COMPUTED_VALUE); + let result = interpolate_scalar_value(property_id, from, to, delta, &[]); + // https://drafts.csswg.org/web-animations-1/#by-computed-value + // If the number of components or the types of corresponding components do not match, or if any component value uses discrete animation and the two corresponding values do not match, then the property values combine as discrete. + if !result.handled || result.value.is_null() && context.is_some_and(|context| context.allow_discrete) { + return discrete_value(context, from, to, delta); + } + result +} + +enum BatchAnimationValue<'a> { + Borrowed(&'a StyleValueData), + Owned(Arc), +} + +impl BatchAnimationValue<'_> { + fn data(&self) -> &StyleValueData { + match self { + Self::Borrowed(value) => value, + Self::Owned(value) => value, + } + } +} + +fn composite_batch_value<'a>( + context: &FfiAnimationContext, + property_id: u16, + underlying: &'a StyleValueData, + animated: &'a StyleValueData, + operation: FfiCompositeOperation, +) -> BatchAnimationValue<'a> { + let result = if matches!( + property_id, + crate::property_metadata::property_id::FILTER | crate::property_metadata::property_id::BACKDROP_FILTER + ) { + composite_filter_list(context, underlying, animated, operation) + } else { + composite_scalar_value(underlying, animated, operation) + }; + assert!(result.handled); + if result.value.is_null() { + return BatchAnimationValue::Borrowed(animated); + } + // SAFETY: A handled non-null composition result transfers one Arc reference. + BatchAnimationValue::Owned(unsafe { Arc::from_raw(result.value) }) +} + +fn evaluate_animation_value( + context: &FfiAnimationContext, + input: &FfiAnimationValueInput, + underlying_override: Option<&StyleValueData>, +) -> FfiAnimatedProperty { + let keyframes = unsafe { std::slice::from_raw_parts(input.keyframes, input.keyframe_count) }; + assert!(keyframes.len() >= 2); + + // https://drafts.csswg.org/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect + // 10. Let interval endpoints be an empty sequence of keyframes. + // 11. Populate interval endpoints by following the steps from the first matching condition from below: + // Otherwise, + // 1. Append to interval endpoints the last keyframe in property-specific keyframes whose computed keyframe offset is less than or equal + // to iteration progress and less than 1. If there is no such keyframe (because, for example, the iteration progress is negative), + // add the last keyframe whose computed keyframe offset is 0. + // 2. Append to interval endpoints the next keyframe in property-specific keyframes after the one added in the previous step. + let mut start_index = 0; + let mut end_index = 1; + for next_index in 2..keyframes.len() { + if input.current_key < keyframes[end_index].key as f64 { + break; + } + start_index = end_index; + end_index = next_index; + } + let start_keyframe = &keyframes[start_index]; + let end_keyframe = &keyframes[end_index]; + let interval_progress = + (input.current_key - start_keyframe.key as f64) / (end_keyframe.key - start_keyframe.key) as f64; + let progress = evaluate_easing_descriptor(&start_keyframe.easing, interval_progress, false) as f32; + if end_keyframe.value.is_null() { + if start_keyframe.value.is_null() { + return FfiAnimatedProperty { + property_id: input.property_id, + value: std::ptr::null(), + progress, + start_index, + end_index, + handled: true, + apply: false, + }; + } + return FfiAnimatedProperty { + property_id: input.property_id, + value: unsafe { crate::style_value::rust_style_value_retain(start_keyframe.value) }, + progress, + start_index, + end_index, + handled: true, + apply: true, + }; + } + let underlying = underlying_override.unwrap_or_else(|| unsafe { &*input.underlying }); + let start = unsafe { + if start_keyframe.value.is_null() { + &*input.initial + } else { + &*start_keyframe.value + } + }; + let end = unsafe { &*end_keyframe.value }; + let start = composite_batch_value(context, input.property_id, underlying, start, start_keyframe.composite); + let end = composite_batch_value(context, input.property_id, underlying, end, end_keyframe.composite); + let result = interpolate_value(Some(context), input.property_id, start.data(), end.data(), progress); + assert!(result.handled); + FfiAnimatedProperty { + property_id: input.property_id, + value: result.value, + progress, + start_index, + end_index, + handled: true, + apply: true, + } +} + +/// Resolve an element's keyframe declarations, request their computed values in one C++ batch, +/// then evaluate and compose every animation interval without consulting C++ or the DOM again. +/// +/// Computed values are requested in at most one callback. Results are written into caller-owned +/// storage returned with the computed values, transferring every non-null result value. +/// +/// # Safety +/// `batch` and `callbacks` must point to live values. Their declaration and bitmap ranges and every +/// input style value returned by `compute_values` must remain live for the call. Its result storage +/// must have room for every input value, and C++ must adopt every non-null result after return. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_evaluate_animations( + batch: *const FfiAnimationBatch, + callbacks: *const FfiAnimationCallbacks, +) -> usize { + crate::abort_on_panic(|| { + crate::ffi_stats::rust_style_ffi_note_animation_evaluation(); + let batch = unsafe { &*batch }; + let callbacks = unsafe { &*callbacks }; + let declarations = unsafe { std::slice::from_raw_parts(batch.declarations, batch.declaration_count) }; + let important_property_bitmap = unsafe { + std::slice::from_raw_parts(batch.important_property_bitmap, batch.important_property_bitmap_length) + }; + let resolved = resolve_animation_declarations( + declarations, + batch.writing_mode, + batch.direction, + important_property_bitmap, + ); + if resolved.properties.is_empty() { + return 0; + } + crate::ffi_stats::bump(crate::ffi_stats::FfiOp::AnimationComputeBatchCallback); + let computed = unsafe { + (callbacks.compute_values)( + callbacks.context, + resolved.properties.as_ptr(), + resolved.properties.len(), + ) + }; + if computed.value_count == 0 { + return 0; + } + let inputs = unsafe { std::slice::from_raw_parts(computed.values, computed.value_count) }; + assert!(computed.result_capacity >= inputs.len()); + assert!(!computed.results.is_null()); + + // https://www.w3.org/TR/web-animations-1/#effect-stacks + // NB: Inputs arrive in composite order. Keep each result as the underlying value for the + // next effect affecting the same property. + let mut previous_values = Vec::<(u16, *const StyleValueData)>::new(); + for (index, input) in inputs.iter().enumerate() { + let previous_value = previous_values + .iter() + .rev() + .find(|(property_id, _)| *property_id == input.property_id) + .map(|(_, value)| unsafe { &**value }); + let result = evaluate_animation_value(&computed.context, input, previous_value); + if result.apply && !result.value.is_null() { + previous_values.push((input.property_id, result.value)); + } + unsafe { computed.results.add(index).write(result) }; + } + inputs.len() + }) +} + +/// Attempt Rust-owned style value interpolation without consulting C++ or the DOM. +/// +/// # Safety +/// `context` must be null or point at a live `FfiAnimationContext`. `from` and `to` must point at live +/// `StyleValueData` allocations. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_interpolate_scalar_style_value( + context: *const FfiAnimationContext, + property_id: u16, + from: *const StyleValueData, + to: *const StyleValueData, + delta: f32, +) -> FfiAnimationValueResult { + crate::abort_on_panic(|| { + interpolate_value( + unsafe { context.as_ref() }, + property_id, + unsafe { &*from }, + unsafe { &*to }, + delta, + ) + }) +} + +/// Test-only bridge for exercising Rust-owned style value composition without constructing an +/// animation batch. Production animation evaluation uses `rust_evaluate_animations`. +/// +/// # Safety +/// `underlying` and `animated` must point at live `StyleValueData` allocations. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_test_composite_style_value( + underlying: *const StyleValueData, + animated: *const StyleValueData, + operation: FfiCompositeOperation, +) -> FfiAnimationValueResult { + crate::abort_on_panic(|| composite_scalar_value(unsafe { &*underlying }, unsafe { &*animated }, operation)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn resolves_keyframe_property_conflicts_as_one_batch() { + use crate::property_metadata::property_id; + let value = || { + let pointer = Arc::into_raw(Arc::new(StyleValueData::Number { value: 1.0 })); + unsafe { RetainedStyleValueData::from_retained_pointer(pointer) } + }; + let candidates = [ + AnimationPropertyConflictCandidate { + keyframe_index: 0, + physical_property_id: property_id::BORDER_TOP_COLOR, + source_property_id: property_id::BORDER, + source_longhand_id: property_id::BORDER_TOP_COLOR, + value: value(), + use_initial: false, + suppressed_by_important: false, + }, + AnimationPropertyConflictCandidate { + keyframe_index: 0, + physical_property_id: property_id::BORDER_TOP_COLOR, + source_property_id: property_id::BORDER_TOP, + source_longhand_id: property_id::BORDER_TOP_COLOR, + value: value(), + use_initial: false, + suppressed_by_important: false, + }, + AnimationPropertyConflictCandidate { + keyframe_index: 0, + physical_property_id: property_id::BORDER_TOP_COLOR, + source_property_id: property_id::BORDER_TOP_COLOR, + source_longhand_id: property_id::BORDER_TOP_COLOR, + value: value(), + use_initial: false, + suppressed_by_important: false, + }, + AnimationPropertyConflictCandidate { + keyframe_index: 0, + physical_property_id: property_id::BORDER_TOP_COLOR, + source_property_id: property_id::BORDER_TOP_COLOR, + source_longhand_id: property_id::BORDER_TOP_COLOR, + value: value(), + use_initial: true, + suppressed_by_important: false, + }, + AnimationPropertyConflictCandidate { + keyframe_index: 1, + physical_property_id: property_id::BORDER_TOP_COLOR, + source_property_id: property_id::BORDER, + source_longhand_id: property_id::BORDER_TOP_COLOR, + value: value(), + use_initial: true, + suppressed_by_important: false, + }, + ]; + let mut selected = [false; 5]; + let mut value_sources = [FfiAnimationSpecifiedValueSource::Value; 5]; + resolve_animation_property_conflicts(&candidates, &mut selected, &mut value_sources); + assert_eq!(selected, [false, false, true, false, true]); + } + + #[test] + fn resolves_animation_css_wide_keywords() { + use crate::property_metadata::property_id; + use crate::style_compute::keyword; + let keyword_value = |keyword| StyleValueData::Keyword { keyword }; + + assert_eq!( + animation_specified_value_source(&keyword_value(keyword::INHERIT), property_id::MARGIN_LEFT), + FfiAnimationSpecifiedValueSource::Inherited + ); + assert_eq!( + animation_specified_value_source(&keyword_value(keyword::UNSET), property_id::COLOR), + FfiAnimationSpecifiedValueSource::Inherited + ); + assert_eq!( + animation_specified_value_source(&keyword_value(keyword::UNSET), property_id::MARGIN_LEFT), + FfiAnimationSpecifiedValueSource::Initial + ); + assert_eq!( + animation_specified_value_source(&keyword_value(keyword::INITIAL), property_id::COLOR), + FfiAnimationSpecifiedValueSource::Initial + ); + assert_eq!( + animation_specified_value_source(&keyword_value(keyword::REVERT), property_id::COLOR), + FfiAnimationSpecifiedValueSource::Underlying + ); + assert_eq!( + animation_specified_value_source(&keyword_value(keyword::REVERT_LAYER), property_id::COLOR), + FfiAnimationSpecifiedValueSource::Underlying + ); + } + + #[test] + fn retains_synthesized_pending_substitution_values() { + let original = Arc::into_raw(Arc::new(StyleValueData::Number { value: 1.0 })); + let pending = Arc::new(StyleValueData::PendingSubstitution { + original_shorthand_value: unsafe { RetainedStyleValueData::from_retained_pointer(original) }, + }); + let declaration = FfiAnimationDeclaration { + keyframe_index: 0, + property_id: crate::property_metadata::property_id::BORDER, + value: &raw const *pending, + use_initial: false, + is_transition: false, + }; + let resolved = resolve_animation_declarations(&[declaration], 0, 0, &[]); + assert!(!resolved.properties.is_empty()); + let mut found_synthesized = false; + for property in &resolved.properties { + let value = unsafe { &*property.value }; + if let StyleValueData::PendingSubstitution { + original_shorthand_value, + } = value + && matches!( + original_shorthand_value.data(), + StyleValueData::PendingSubstitution { .. } + ) + { + found_synthesized = true; + } + } + assert!(found_synthesized); + } + + #[test] + fn reads_important_property_snapshot() { + use crate::property_metadata::{FIRST_LONGHAND_PROPERTY_ID, property_id}; + let important_index = usize::from(property_id::COLOR - FIRST_LONGHAND_PROPERTY_ID); + let mut bitmap = vec![0; important_index / 8 + 1]; + bitmap[important_index / 8] |= 1 << (important_index % 8); + assert!(property_is_important(property_id::COLOR, &bitmap)); + assert!(!property_is_important(property_id::MARGIN_LEFT, &bitmap)); + assert!(animation_property_is_suppressed(false, property_id::COLOR, &bitmap)); + assert!(!animation_property_is_suppressed(true, property_id::COLOR, &bitmap)); + assert!(!property_is_important(FIRST_LONGHAND_PROPERTY_ID - 1, &bitmap)); + } + + fn animation_context(allow_discrete: bool) -> FfiAnimationContext { + let font_metrics = || FfiAnimationFontMetrics { + font_size: 0.0, + x_height: 0.0, + cap_height: 0.0, + zero_advance: 0.0, + line_height: 0.0, + }; + FfiAnimationContext { + allow_discrete, + current_color: std::ptr::null(), + has_length_resolution_context: false, + length_resolution_context: FfiAnimationLengthResolutionContext { + viewport_width: 0.0, + viewport_height: 0.0, + font_metrics: font_metrics(), + root_font_metrics: font_metrics(), + font_metrics_depend_on_viewport_metrics: false, + root_font_metrics_depend_on_viewport_metrics: false, + }, + has_transform_reference_box: false, + transform_reference_box_width: 0.0, + transform_reference_box_height: 0.0, + } + } + + fn calculated_number(value: f64) -> StyleValueData { + StyleValueData::Calculated { + rust_calculation: crate::calc::CalcNodeHandle::from_arc(Arc::new(crate::calc::CalcNode::Numeric( + crate::calc::CalcNumericValue::Number { value, number_type: 0 }, + ))), + resolve_as_is_number: false, + resolve_as_base: 0, + resolved_type: crate::calc::FfiNumericType::from_calc(Some(crate::calc::CalcNumericType::default())), + has_percentages_resolve_as: false, + percentages_resolve_as: 0, + resolve_numbers_as_integers: false, + accepted_ranges: RetainedNumericRangeList::empty(), + } + } + + #[test] + fn evaluates_linear_easing() { + let points = [ + FfiLinearEasingPoint { + input: 0.0, + output: 0.0, + }, + FfiLinearEasingPoint { + input: 0.0, + output: 0.5, + }, + FfiLinearEasingPoint { + input: 1.0, + output: 1.0, + }, + ]; + assert_eq!(evaluate_linear_easing(&points, 0.0, true), 0.0); + assert_eq!(evaluate_linear_easing(&points, 0.0, false), 0.5); + assert_eq!(evaluate_linear_easing(&points, 0.5, false), 0.75); + } + + #[test] + fn evaluates_cubic_bezier_easing() { + assert!((evaluate_cubic_bezier_easing(0.42, 0.0, 0.58, 1.0, 0.5) - 0.5).abs() < 1e-7); + assert_eq!(evaluate_cubic_bezier_easing(0.5, 1.0, 1.0, 1.0, -0.5), -1.0); + } + + #[test] + fn evaluates_steps_easing() { + assert_eq!(evaluate_steps_easing(4, 1, 0.5, false), 0.5); + assert_eq!(evaluate_steps_easing(4, 1, 0.5, true), 0.25); + assert_eq!(evaluate_steps_easing(4, STEP_POSITION_JUMP_START, 0.0, false), 0.25); + } + + #[test] + fn evaluates_composited_animation_value() { + let underlying = StyleValueData::Number { value: 2.0 }; + let start = StyleValueData::Number { value: 3.0 }; + let middle = StyleValueData::Number { value: 5.0 }; + let end = StyleValueData::Number { value: 9.0 }; + let linear_easing = || FfiEasingDescriptor { + kind: FfiEasingKind::CubicBezier, + linear_points: std::ptr::null(), + linear_point_count: 0, + x1: 0.0, + y1: 0.0, + x2: 1.0, + y2: 1.0, + interval_count: 0, + step_position: 0, + }; + let keyframes = [ + FfiAnimationKeyframeValue { + key: 0, + value: &raw const start, + easing: linear_easing(), + composite: FfiCompositeOperation::Add, + }, + FfiAnimationKeyframeValue { + key: 50, + value: &raw const middle, + easing: linear_easing(), + composite: FfiCompositeOperation::Add, + }, + FfiAnimationKeyframeValue { + key: 100, + value: &raw const end, + easing: linear_easing(), + composite: FfiCompositeOperation::Add, + }, + ]; + let input = FfiAnimationValueInput { + property_id: crate::property_metadata::property_id::FLEX_GROW, + underlying: &raw const underlying, + initial: &raw const underlying, + current_key: 75.0, + keyframes: keyframes.as_ptr(), + keyframe_count: keyframes.len(), + }; + let result = evaluate_animation_value(&animation_context(true), &input, None); + assert!(result.handled); + assert!(result.apply); + assert!(!result.value.is_null()); + assert_eq!(result.start_index, 1); + assert_eq!(result.end_index, 2); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Number { value } if *value == 9.0)); + } + + #[test] + fn evaluates_missing_keyframe_values() { + let underlying = Arc::new(StyleValueData::Number { value: 2.0 }); + let initial = Arc::new(StyleValueData::Number { value: 1.0 }); + let start = Arc::new(StyleValueData::Number { value: 3.0 }); + let end = Arc::new(StyleValueData::Number { value: 5.0 }); + let easing = || FfiEasingDescriptor { + kind: FfiEasingKind::CubicBezier, + linear_points: std::ptr::null(), + linear_point_count: 0, + x1: 0.0, + y1: 0.0, + x2: 1.0, + y2: 1.0, + interval_count: 0, + step_position: 0, + }; + let evaluate = |start_value, end_value| { + let keyframes = [ + FfiAnimationKeyframeValue { + key: 0, + value: start_value, + easing: easing(), + composite: FfiCompositeOperation::Replace, + }, + FfiAnimationKeyframeValue { + key: 100, + value: end_value, + easing: easing(), + composite: FfiCompositeOperation::Replace, + }, + ]; + let input = FfiAnimationValueInput { + property_id: crate::property_metadata::property_id::FLEX_GROW, + underlying: Arc::as_ptr(&underlying), + initial: Arc::as_ptr(&initial), + current_key: 50.0, + keyframes: keyframes.as_ptr(), + keyframe_count: keyframes.len(), + }; + evaluate_animation_value(&animation_context(true), &input, None) + }; + + let result = evaluate(std::ptr::null(), Arc::as_ptr(&end)); + assert!(result.handled); + assert!(result.apply); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Number { value } if *value == 3.0)); + + let result = evaluate(Arc::as_ptr(&start), std::ptr::null()); + assert!(result.handled); + assert!(result.apply); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Number { value } if *value == 3.0)); + + let result = evaluate(std::ptr::null(), std::ptr::null()); + assert!(result.handled); + assert!(!result.apply); + assert!(result.value.is_null()); + } + + #[test] + fn evaluates_discrete_animation_values() { + let from = Arc::new(StyleValueData::Keyword { keyword: 1 }); + let to = Arc::new(StyleValueData::Keyword { keyword: 2 }); + let property_id = crate::property_metadata::property_id::ALIGN_CONTENT; + + let result = interpolate_value(Some(&animation_context(true)), property_id, &from, &to, 0.25); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Keyword { keyword: 1 })); + + let result = interpolate_value(Some(&animation_context(true)), property_id, &from, &to, 0.75); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Keyword { keyword: 2 })); + + let result = interpolate_value(Some(&animation_context(false)), property_id, &from, &to, 0.75); + assert!(result.handled); + assert!(result.value.is_null()); + } + + #[test] + fn evaluates_incompatible_by_computed_values_as_discrete() { + let property_id = crate::property_metadata::property_id::FLEX_GROW; + let from = Arc::new(StyleValueData::Keyword { keyword: 1 }); + let to = Arc::new(StyleValueData::Keyword { keyword: 2 }); + + let result = interpolate_value(Some(&animation_context(true)), property_id, &from, &to, 0.25); + assert!(result.handled); + assert_eq!(result.value, Arc::as_ptr(&from)); + unsafe { crate::style_value::rust_style_value_release(result.value) }; + + let result = interpolate_value(Some(&animation_context(true)), property_id, &from, &to, 0.75); + assert!(result.handled); + assert_eq!(result.value, Arc::as_ptr(&to)); + unsafe { crate::style_value::rust_style_value_release(result.value) }; + + let result = interpolate_value(Some(&animation_context(false)), property_id, &from, &to, 0.75); + assert!(result.handled); + assert!(result.value.is_null()); + } + + #[test] + fn preserves_not_animatable_endpoint_behavior() { + let from = Arc::new(StyleValueData::Keyword { keyword: 1 }); + let to = Arc::new(StyleValueData::Keyword { keyword: 2 }); + let result = interpolate_value( + Some(&animation_context(true)), + crate::property_metadata::property_id::ANIMATION_DURATION, + &from, + &to, + 0.25, + ); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Keyword { keyword: 2 })); + } + + #[test] + fn evaluates_declined_custom_animation_values_as_discrete() { + let from = Arc::new(StyleValueData::Keyword { keyword: 1 }); + let to = Arc::new(StyleValueData::Keyword { keyword: 2 }); + let property_id = crate::property_metadata::property_id::SCALE; + + let result = interpolate_value(Some(&animation_context(true)), property_id, &from, &to, 0.75); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Keyword { keyword: 2 })); + + let result = interpolate_value(Some(&animation_context(false)), property_id, &from, &to, 0.75); + assert!(result.handled); + assert!(result.value.is_null()); + } + + #[test] + fn normalizes_repeatable_scalar_and_list_values() { + let property_id = crate::property_metadata::property_id::OBJECT_POSITION; + let from = Arc::new(StyleValueData::Number { value: 10.0 }); + let to = Arc::new(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(vec![ + retained_number(20.0), + retained_number(30.0), + ]), + separator: 1, + collapsible: false, + }); + + let result = interpolate_value(Some(&animation_context(true)), property_id, &from, &to, 0.5); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + let StyleValueData::ValueList { values, .. } = &*value else { + panic!("expected a repeatable value list"); + }; + assert!(matches!(values.as_slice()[0].data(), StyleValueData::Number { value } if *value == 15.0)); + assert!(matches!(values.as_slice()[1].data(), StyleValueData::Number { value } if *value == 20.0)); + } + + #[test] + fn interpolates_repeatable_scalar_values() { + let from = Arc::new(StyleValueData::Number { value: 10.0 }); + let to = Arc::new(StyleValueData::Number { value: 20.0 }); + let result = interpolate_value( + Some(&animation_context(true)), + crate::property_metadata::property_id::OBJECT_POSITION, + &from, + &to, + 0.5, + ); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Number { value } if *value == 15.0)); + } + + #[test] + fn interpolates_mixed_length_percentage_values() { + let from = Arc::new(StyleValueData::Length { value: 10.0, unit: 0 }); + let to = Arc::new(StyleValueData::Percentage { value: 50.0 }); + let result = interpolate_value( + Some(&animation_context(true)), + crate::property_metadata::property_id::WIDTH, + &from, + &to, + 0.5, + ); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Calculated { .. })); + } + + #[test] + fn combines_lengths_with_different_units_discretely() { + let from = Arc::new(StyleValueData::Length { value: 10.0, unit: 0 }); + let to = Arc::new(StyleValueData::Length { value: 20.0, unit: 1 }); + let result = interpolate_value( + Some(&animation_context(true)), + crate::property_metadata::property_id::WIDTH, + &from, + &to, + 0.75, + ); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Length { value, unit } if *value == 20.0 && *unit == 1)); + } + + #[test] + fn interpolates_rotate_3d_with_a_zero_axis() { + let retained_angle = |value| { + let pointer = Arc::into_raw(Arc::new(StyleValueData::Angle { value, unit: 0 })); + unsafe { RetainedStyleValueData::from_retained_pointer(pointer) } + }; + let from = RetainedStyleValueDataList::from_retained_values(vec![ + retained_number(0.0), + retained_number(0.0), + retained_number(0.0), + retained_angle(90.0), + ]); + let to = RetainedStyleValueDataList::from_retained_values(vec![ + retained_number(1.0), + retained_number(0.0), + retained_number(0.0), + retained_angle(180.0), + ]); + let result = interpolate_rotate_3d(crate::property_metadata::property_id::TRANSFORM, 0, &from, &to, 0.5) + .expect("rotate3d values should interpolate"); + let StyleValueData::Transformation { values, .. } = result else { + panic!("expected a transform function"); + }; + assert!(values.as_slice().iter().all(|value| match value.data() { + StyleValueData::Number { value } | StyleValueData::Angle { value, .. } => value.is_finite(), + _ => false, + })); + } + + #[test] + fn composes_mixed_length_percentage_values() { + let underlying = Arc::new(StyleValueData::Length { value: 10.0, unit: 0 }); + let animated = Arc::new(StyleValueData::Percentage { value: 50.0 }); + let result = composite_scalar_value(&underlying, &animated, FfiCompositeOperation::Add); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Calculated { .. })); + } + + #[test] + fn owns_unsupported_composition_decisions() { + let underlying = Arc::new(StyleValueData::Keyword { + keyword: crate::style_compute::none_keyword(), + }); + let animated = Arc::new(StyleValueData::Number { value: 4.0 }); + let result = composite_scalar_value(&underlying, &animated, FfiCompositeOperation::Add); + assert!(result.handled); + assert!(result.value.is_null()); + } + + #[test] + fn combines_general_calculated_numeric_values() { + let from = Arc::new(calculated_number(2.0)); + let to = Arc::new(StyleValueData::Number { value: 4.0 }); + let result = interpolate_value( + Some(&animation_context(true)), + crate::property_metadata::property_id::FLEX_GROW, + &from, + &to, + 0.5, + ); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Calculated { .. })); + + let result = composite_scalar_value(&from, &to, FfiCompositeOperation::Add); + assert!(result.handled); + let value = unsafe { Arc::from_raw(result.value) }; + assert!(matches!(&*value, StyleValueData::Calculated { .. })); + } +} diff --git a/Libraries/LibWeb/CSS/Rust/src/calc.rs b/Libraries/LibWeb/CSS/Rust/src/calc.rs index 4e9301e047692..9e83abb9648b2 100644 --- a/Libraries/LibWeb/CSS/Rust/src/calc.rs +++ b/Libraries/LibWeb/CSS/Rust/src/calc.rs @@ -15,7 +15,7 @@ use std::sync::Arc; -use crate::style_value::RetainedStyleValue; +use crate::style_value::RetainedStyleValueData; include!(concat!(env!("OUT_DIR"), "/dimension_units_generated.rs")); @@ -362,6 +362,7 @@ impl CalcNumericValue { /// The FFI mirror of a numeric type, for the parity test on the C++ side. /// NB: The array dimension is the base type count, spelled literally so the /// generated header does not depend on the crate-private constant. +#[derive(Clone, Copy, PartialEq)] #[repr(C)] pub struct FfiNumericType { pub has_exponent: [bool; 7], @@ -372,7 +373,7 @@ pub struct FfiNumericType { } impl FfiNumericType { - fn from_calc(value: Option) -> Self { + pub(crate) fn from_calc(value: Option) -> Self { let mut result = FfiNumericType { has_exponent: [false; BASE_TYPE_COUNT], exponents: [0; BASE_TYPE_COUNT], @@ -395,7 +396,7 @@ impl FfiNumericType { result } - fn to_calc(&self) -> CalcNumericType { + fn to_calc(self) -> CalcNumericType { let mut result = CalcNumericType::default(); for i in 0..BASE_TYPE_COUNT { if self.has_exponent[i] { @@ -442,6 +443,7 @@ pub type CalcRoundingStrategy = u8; /// One node of a calculation tree. Child nodes are shared immutably. /// /// https://www.w3.org/TR/css-values-4/#calculation-tree +#[derive(PartialEq)] pub enum CalcNode { /// A numeric leaf value. Numeric(CalcNumericValue), @@ -507,14 +509,14 @@ pub enum CalcNode { min: Arc, max: Arc, step: Option>, - /// The random-value-sharing options value, retained from the shell. - sharing: RetainedStyleValue, + /// NB: The random-value-sharing options value retained in the Rust value graph. + sharing: RetainedStyleValueData, }, /// A non-math function whose value participates in a calculation, kept as /// its retained style value together with the numeric type its context /// determined at creation. NonMathFunction { - value: RetainedStyleValue, + value: RetainedStyleValueData, numeric_type: CalcNumericType, }, } @@ -675,7 +677,7 @@ impl CalcNode { pub(crate) fn is_computationally_independent( &self, length_is_independent: &impl Fn(u8) -> bool, - style_value_is_independent: &impl Fn(&RetainedStyleValue) -> bool, + style_value_is_independent: &impl Fn(&RetainedStyleValueData) -> bool, ) -> bool { let leaf_independent = match self { CalcNode::Numeric(CalcNumericValue::Length { unit, .. }) => length_is_independent(*unit), @@ -774,6 +776,12 @@ impl CalcNodeHandle { Self { node: raw } } + pub(crate) fn from_arc(node: Arc) -> Self { + Self { + node: Arc::into_raw(node), + } + } + pub(crate) fn node(&self) -> &CalcNode { unsafe { &*self.node } } @@ -786,6 +794,96 @@ impl CalcNodeHandle { } } +impl PartialEq for CalcNodeHandle { + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self.node, other.node) || self.node() == other.node() + } +} + +/// https://drafts.csswg.org/css-values-4/#combine-math +/// Interpolation of math functions, with each other or with numeric values and other numeric-valued functions, is +/// defined as Vresult = calc((1 - p) * VA + p * VB). +pub(crate) fn interpolate_length_percentage_calculations( + from: Arc, + to: Arc, + delta: f32, +) -> Option<(Arc, FfiNumericType)> { + combine_calculations(from, to, 1.0 - delta as f64, delta as f64, Some(ResolveAs::Base(0))) +} + +pub(crate) fn add_length_percentage_calculations( + underlying: Arc, + animated: Arc, +) -> Option<(Arc, FfiNumericType)> { + combine_calculations(underlying, animated, 1.0, 1.0, Some(ResolveAs::Base(0))) +} + +/// https://drafts.csswg.org/css-values-4/#combine-math +/// Interpolation of math functions, with each other or with numeric values and other numeric-valued functions, is +/// defined as Vresult = calc((1 - p) * VA + p * VB). +pub(crate) fn interpolate_calculations( + from: Arc, + to: Arc, + delta: f32, + has_percentages_resolve_as: bool, + resolve_as_is_number: bool, + resolve_as_base: u8, +) -> Option<(Arc, FfiNumericType)> { + combine_calculations( + from, + to, + 1.0 - delta as f64, + delta as f64, + resolve_as_from_fields(has_percentages_resolve_as, resolve_as_is_number, resolve_as_base), + ) +} + +pub(crate) fn add_calculations( + underlying: Arc, + animated: Arc, + has_percentages_resolve_as: bool, + resolve_as_is_number: bool, + resolve_as_base: u8, +) -> Option<(Arc, FfiNumericType)> { + combine_calculations( + underlying, + animated, + 1.0, + 1.0, + resolve_as_from_fields(has_percentages_resolve_as, resolve_as_is_number, resolve_as_base), + ) +} + +fn combine_calculations( + from: Arc, + to: Arc, + from_multiplier: f64, + to_multiplier: f64, + resolve_as: Option, +) -> Option<(Arc, FfiNumericType)> { + let number = |value| shared(CalcNode::Numeric(CalcNumericValue::Number { value, number_type: 0 })); + let from_contribution = shared(CalcNode::Product(vec![from, number(from_multiplier)])); + let to_contribution = shared(CalcNode::Product(vec![to, number(to_multiplier)])); + let root = shared(CalcNode::Sum(vec![from_contribution, to_contribution])); + + let percentage_leaf_type = percentage_leaf_type_for(resolve_as); + let evaluation_context = CalcEvaluationContext { + percentage_leaf_type: &percentage_leaf_type, + resolve_as, + percentage_basis: None, + length_resolution: LengthResolution::default(), + random_base_value: None, + }; + let callbacks = CalcSimplifyCallbacks { + resolve_non_math_function: &|_| None, + resolve_channel_keyword: &|_| None, + absolutize_random_sharing: &|_| None, + }; + let simplified = root.simplify(&evaluation_context, &callbacks); + let numeric_type = simplified.numeric_type(&percentage_leaf_type)?; + Some((simplified, FfiNumericType::from_calc(Some(numeric_type)))) +} + impl Drop for CalcNodeHandle { fn drop(&mut self) { drop(unsafe { Arc::from_raw(self.node) }); @@ -998,7 +1096,7 @@ pub unsafe extern "C" fn rust_calc_node_create_round( /// # Safety /// The children must be valid transferred handles (`step` may be null), and -/// `sharing` a leaked strong StyleValue reference. +/// `sharing` a transferred strong style value data handle. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_calc_node_create_random( min: *const CalcNode, @@ -1016,13 +1114,13 @@ pub unsafe extern "C" fn rust_calc_node_create_random( } else { Some(unsafe { Arc::from_raw(step) }) }, - sharing: unsafe { RetainedStyleValue::from_shell_pointer(sharing) }, + sharing: unsafe { RetainedStyleValueData::from_retained_pointer(sharing.cast()) }, }) }) } /// # Safety -/// `value` must be a leaked strong StyleValue reference. +/// `value` must be a transferred strong style value data handle. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_calc_node_create_non_math_function( value: *const std::ffi::c_void, @@ -1031,7 +1129,7 @@ pub unsafe extern "C" fn rust_calc_node_create_non_math_function( crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcNodeBuildEntry); crate::abort_on_panic(|| { handle(CalcNode::NonMathFunction { - value: unsafe { RetainedStyleValue::from_shell_pointer(value) }, + value: unsafe { RetainedStyleValueData::from_retained_pointer(value.cast()) }, numeric_type: unsafe { &*numeric_type }.to_calc(), }) }) @@ -1112,16 +1210,16 @@ pub(crate) struct CalcEvaluationContext<'a> { } /// Produces the random base value for a random() node's sharing options. -pub(crate) type RandomBaseValueResolver<'a> = &'a dyn Fn(&RetainedStyleValue) -> Option; +pub(crate) type RandomBaseValueResolver<'a> = &'a dyn Fn(&RetainedStyleValueData) -> Option; -/// The C++ seams the simplification needs: resolving a non-math function to a -/// calculation subtree, and looking up a relative-color channel value. +/// The external values simplification needs, read from the immutable +/// resolution batch prepared before evaluation. pub(crate) struct CalcSimplifyCallbacks<'a> { - pub resolve_non_math_function: &'a dyn Fn(&RetainedStyleValue) -> Option>, + pub resolve_non_math_function: &'a dyn Fn(&RetainedStyleValueData) -> Option>, pub resolve_channel_keyword: &'a dyn Fn(u8) -> Option, /// Absolutizes a random() node's sharing options at computed-value time, /// fixing its per-element random base value; None keeps the original. - pub absolutize_random_sharing: &'a dyn Fn(&RetainedStyleValue) -> Option, + pub absolutize_random_sharing: &'a dyn Fn(&RetainedStyleValueData) -> Option, } fn is_canonical_unit(value: CalcNumericValue) -> bool { @@ -1140,6 +1238,10 @@ fn is_canonical_unit(value: CalcNumericValue) -> bool { } } +pub(crate) fn canonical_pixel_unit() -> u8 { + canonical_unit_code(&crate::style_compute::LENGTH_UNIT_CANONICAL_PX_RATIOS) +} + impl CalcNode { /// Mirrors try_get_value_with_canonical_unit: a numeric child in its /// canonical unit whose percentages are resolved, as an evaluation result. @@ -2329,30 +2431,145 @@ pub struct FfiCalcResolutionContext { /// The length resolution context as an opaque pointer (its type lives in /// the computed-values header), or null. pub length_resolution_context: *const std::ffi::c_void, - pub callback_context: *mut std::ffi::c_void, - /// Resolves a non-math function value to a calculation subtree, or null. - pub resolve_non_math_function: - unsafe extern "C" fn(context: *mut std::ffi::c_void, shell: *const std::ffi::c_void) -> *const CalcNode, - /// Looks up a relative-color channel value. - pub resolve_channel_keyword: - unsafe extern "C" fn(context: *mut std::ffi::c_void, channel: u8, out_value: *mut f64) -> bool, - /// Produces the random base value for a random() node's sharing options, - /// or reports that the context cannot (it needs per-element state). - pub random_base_value: unsafe extern "C" fn( - context: *mut std::ffi::c_void, - sharing: *const std::ffi::c_void, - out_value: *mut f64, - ) -> bool, - /// Absolutizes a random() node's sharing options at computed-value time, - /// returning a leaked strong reference to the fixed value, or null. - pub absolutize_random_sharing: unsafe extern "C" fn( - context: *mut std::ffi::c_void, - sharing: *const std::ffi::c_void, - ) -> *const std::ffi::c_void, - /// Resolves a length the Rust resolver cannot (container-relative units, - /// which need the per-element query container lookup). - pub resolve_length: - unsafe extern "C" fn(context: *mut std::ffi::c_void, value: f64, unit: u8, out_px: *mut f64) -> bool, + /// External leaf resolutions prepared by C++ before Rust evaluates. + pub external_resolutions: *const FfiCalcExternalResolution, + pub external_resolution_count: usize, +} + +/// One external calculation leaf and the resolution C++ prepared for it. +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum FfiCalcExternalResolutionKind { + NonMathFunction, + Channel, + RandomSharing, + Length, +} + +#[repr(C)] +pub struct FfiCalcExternalResolution { + pub kind: FfiCalcExternalResolutionKind, + pub source: *const std::ffi::c_void, + pub input_value: f64, + pub unit_or_channel: u8, + pub has_number: bool, + pub number: f64, + pub resolved_node: *const CalcNode, + pub resolved_style_value: *const std::ffi::c_void, +} + +#[repr(C)] +pub struct FfiCalcExternalResolutions { + pub resolutions: *mut FfiCalcExternalResolution, + pub resolution_count: usize, + pub storage: *mut std::ffi::c_void, +} + +struct CalcExternalResolutionStorage { + resolutions: Box<[FfiCalcExternalResolution]>, +} + +fn collect_external_resolutions(node: &CalcNode, resolutions: &mut Vec) { + let mut append = |kind, source, input_value, unit_or_channel| { + resolutions.push(FfiCalcExternalResolution { + kind, + source, + input_value, + unit_or_channel, + has_number: false, + number: 0.0, + resolved_node: std::ptr::null(), + resolved_style_value: std::ptr::null(), + }); + }; + match node { + CalcNode::NonMathFunction { value, .. } => append( + FfiCalcExternalResolutionKind::NonMathFunction, + value.pointer().cast(), + 0.0, + 0, + ), + CalcNode::ChannelKeyword(channel) => { + append(FfiCalcExternalResolutionKind::Channel, std::ptr::null(), 0.0, *channel); + } + CalcNode::Random { sharing, .. } => append( + FfiCalcExternalResolutionKind::RandomSharing, + sharing.pointer().cast(), + 0.0, + 0, + ), + CalcNode::Numeric(CalcNumericValue::Length { value, unit }) + if crate::style_compute::LENGTH_UNIT_NAMES[*unit as usize].starts_with("cq") => + { + append(FfiCalcExternalResolutionKind::Length, std::ptr::null(), *value, *unit); + } + _ => {} + } + node.for_each_child(&mut |child| collect_external_resolutions(child, resolutions)); +} + +/// Returns every calculation leaf that needs C++-owned state. C++ fills the +/// output fields once, before passing the batch back in a resolution context. +/// +/// # Safety +/// `root` must be a valid calculation node pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_calc_external_resolutions( + root: *const CalcNode, + basis_kind: u8, + basis_value: f64, + basis_unit: u8, +) -> FfiCalcExternalResolutions { + crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcOperationEntry); + crate::abort_on_panic(|| { + let mut resolutions = Vec::new(); + let root = unsafe { &*root }; + collect_external_resolutions(root, &mut resolutions); + if basis_kind == 3 + && root.contains_percentage() + && crate::style_compute::LENGTH_UNIT_NAMES[basis_unit as usize].starts_with("cq") + { + resolutions.push(FfiCalcExternalResolution { + kind: FfiCalcExternalResolutionKind::Length, + source: std::ptr::null(), + input_value: basis_value, + unit_or_channel: basis_unit, + has_number: false, + number: 0.0, + resolved_node: std::ptr::null(), + resolved_style_value: std::ptr::null(), + }); + } + let storage = Box::new(CalcExternalResolutionStorage { + resolutions: resolutions.into_boxed_slice(), + }); + let result = FfiCalcExternalResolutions { + resolutions: storage.resolutions.as_ptr().cast_mut(), + resolution_count: storage.resolutions.len(), + storage: std::ptr::null_mut(), + }; + let storage = Box::into_raw(storage); + FfiCalcExternalResolutions { + storage: storage.cast(), + ..result + } + }) +} + +/// # Safety +/// `storage` must be returned by `rust_calc_external_resolutions`. Any output +/// handles written by C++ must each own one strong reference. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_calc_external_resolutions_release(storage: *mut std::ffi::c_void) { + let storage = unsafe { Box::from_raw(storage.cast::()) }; + for resolution in &storage.resolutions { + if !resolution.resolved_node.is_null() { + drop(unsafe { Arc::from_raw(resolution.resolved_node) }); + } + if !resolution.resolved_style_value.is_null() { + unsafe { crate::style_value::rust_style_value_release(resolution.resolved_style_value.cast()) }; + } + } } /// The resolve-as target from a calculated value's creation-time fields. @@ -2408,21 +2625,32 @@ fn with_ffi_evaluation( }), _ => unreachable!("invalid percentage basis kind"), }; - let random_base_value = |sharing: &RetainedStyleValue| -> Option { - let mut value = 0.0; - if unsafe { (context.random_base_value)(context.callback_context, sharing.shell_pointer(), &raw mut value) } { - Some(value) - } else { - None - } + let external_resolutions = if context.external_resolutions.is_null() { + &[] + } else { + unsafe { std::slice::from_raw_parts(context.external_resolutions, context.external_resolution_count) } + }; + let random_base_value = |sharing: &RetainedStyleValueData| -> Option { + external_resolutions + .iter() + .find(|resolution| { + matches!(resolution.kind, FfiCalcExternalResolutionKind::RandomSharing) + && (resolution.source == sharing.pointer().cast() + || resolution.resolved_style_value == sharing.pointer().cast()) + }) + .filter(|resolution| resolution.has_number) + .map(|resolution| resolution.number) }; let resolve_length_fallback = |value: f64, unit: u8| -> Option { - let mut px = 0.0; - if unsafe { (context.resolve_length)(context.callback_context, value, unit, &raw mut px) } { - Some(px) - } else { - None - } + external_resolutions + .iter() + .find(|resolution| { + matches!(resolution.kind, FfiCalcExternalResolutionKind::Length) + && resolution.input_value.to_bits() == value.to_bits() + && resolution.unit_or_channel == unit + }) + .filter(|resolution| resolution.has_number) + .map(|resolution| resolution.number) }; let evaluation_context = CalcEvaluationContext { percentage_leaf_type: &percentage_leaf_type, @@ -2440,33 +2668,45 @@ fn with_ffi_evaluation( }, random_base_value: Some(&random_base_value), }; - let absolutize_random_sharing = |sharing: &RetainedStyleValue| -> Option { - let absolutized = - unsafe { (context.absolutize_random_sharing)(context.callback_context, sharing.shell_pointer()) }; - if absolutized.is_null() { - None - } else { - Some(unsafe { RetainedStyleValue::from_shell_pointer(absolutized) }) - } + let absolutize_random_sharing = |sharing: &RetainedStyleValueData| -> Option { + external_resolutions + .iter() + .find(|resolution| { + matches!(resolution.kind, FfiCalcExternalResolutionKind::RandomSharing) + && resolution.source == sharing.pointer().cast() + }) + .filter(|resolution| !resolution.resolved_style_value.is_null()) + .map(|resolution| unsafe { + RetainedStyleValueData::from_retained_pointer(crate::style_value::rust_style_value_retain( + resolution.resolved_style_value.cast(), + )) + }) }; let callbacks = CalcSimplifyCallbacks { absolutize_random_sharing: &absolutize_random_sharing, resolve_non_math_function: &|value| { - let resolved = - unsafe { (context.resolve_non_math_function)(context.callback_context, value.shell_pointer()) }; + let resolved = external_resolutions + .iter() + .find(|resolution| { + matches!(resolution.kind, FfiCalcExternalResolutionKind::NonMathFunction) + && resolution.source == value.pointer().cast() + })? + .resolved_node; if resolved.is_null() { - None - } else { - Some(unsafe { Arc::from_raw(resolved) }) + return None; } + unsafe { Arc::increment_strong_count(resolved) }; + Some(unsafe { Arc::from_raw(resolved) }) }, resolve_channel_keyword: &|channel| { - let mut value = 0.0; - if unsafe { (context.resolve_channel_keyword)(context.callback_context, channel, &raw mut value) } { - Some(value) - } else { - None - } + external_resolutions + .iter() + .find(|resolution| { + matches!(resolution.kind, FfiCalcExternalResolutionKind::Channel) + && resolution.unit_or_channel == channel + }) + .filter(|resolution| resolution.has_number) + .map(|resolution| resolution.number) }, }; f(&evaluation_context, &callbacks) @@ -2545,13 +2785,13 @@ fn resolve_simplified_calculation( Some((raw_value, result.numeric_type)) } -/// Resolves a calculated value with no external context: the equivalent of a -/// resolution against an empty C++ resolution context, where the callbacks for -/// non-math functions, relative-color channels, and random() all fail. Used by -/// the style computation core's own property helpers. -fn resolve_calculated_without_context( +/// Resolves a calculated value using the supplied length metrics while the +/// callbacks for non-math functions, relative-color channels, and random() +/// remain unavailable. +fn resolve_calculated_with_length_resolution( calculated: &crate::style_value::StyleValueData, percentage_basis: Option, + length_resolution: LengthResolution, ) -> Option<(f64, CalcNumericType, Option)> { use crate::style_value::StyleValueData; let StyleValueData::Calculated { @@ -2573,7 +2813,7 @@ fn resolve_calculated_without_context( percentage_leaf_type: &percentage_leaf_type, resolve_as, percentage_basis, - length_resolution: LengthResolution::default(), + length_resolution, random_base_value: None, }; let callbacks = CalcSimplifyCallbacks { @@ -2593,6 +2833,13 @@ fn resolve_calculated_without_context( Some((value, numeric_type.expect("canonical result has a type"), resolve_as)) } +fn resolve_calculated_without_context( + calculated: &crate::style_value::StyleValueData, + percentage_basis: Option, +) -> Option<(f64, CalcNumericType, Option)> { + resolve_calculated_with_length_resolution(calculated, percentage_basis, LengthResolution::default()) +} + /// Resolves a calculated value that must produce a number, with no external /// context; the equivalent of the C++ resolve_number with an empty context. pub(crate) fn resolve_calculated_number_without_context( @@ -2602,6 +2849,23 @@ pub(crate) fn resolve_calculated_number_without_context( numeric_type.matches_number(resolve_as).then_some(value) } +/// Resolves a calculated value that must produce a number using the immutable +/// per-element length metrics supplied to animation evaluation. +pub(crate) fn resolve_calculated_number_with_context( + calculated: &crate::style_value::StyleValueData, + context: &crate::style_compute::FfiLengthResolutionContext, +) -> Option { + let (value, numeric_type, resolve_as) = resolve_calculated_with_length_resolution( + calculated, + None, + LengthResolution { + context: Some(context), + fallback: None, + }, + )?; + numeric_type.matches_number(resolve_as).then_some(value) +} + /// Resolves a calculated value that must produce a percentage, with no /// external context; the equivalent of the C++ resolve_percentage with an /// empty context. @@ -2612,6 +2876,30 @@ pub(crate) fn resolve_calculated_percentage_without_context( numeric_type.matches_percentage().then_some(value) } +/// Resolves a calculated value that must produce an angle, with no external +/// context; the equivalent of the C++ resolve_angle with an empty context. +pub(crate) fn resolve_calculated_angle_without_context(calculated: &crate::style_value::StyleValueData) -> Option { + let (value, numeric_type, resolve_as) = resolve_calculated_without_context(calculated, None)?; + numeric_type.matches_dimension(1, resolve_as).then_some(value) +} + +/// Resolves a calculated value that must produce an angle using the immutable +/// per-element length metrics supplied to animation evaluation. +pub(crate) fn resolve_calculated_angle_with_context( + calculated: &crate::style_value::StyleValueData, + context: &crate::style_compute::FfiLengthResolutionContext, +) -> Option { + let (value, numeric_type, resolve_as) = resolve_calculated_with_length_resolution( + calculated, + None, + LengthResolution { + context: Some(context), + fallback: None, + }, + )?; + numeric_type.matches_dimension(1, resolve_as).then_some(value) +} + /// Resolves a calculated value that must produce a number and rounds it to the /// nearest integer (toward +inf on a .5 fraction), with no external context; /// the equivalent of the C++ resolve_integer with an empty context. @@ -2648,6 +2936,23 @@ pub(crate) fn resolve_calculated_length_without_context( (numeric_type.matches_dimension(0, resolve_as) || numeric_type.matches_percentage()).then_some(value) } +/// Resolves a calculated value that must produce a length using the immutable +/// per-element length metrics supplied to animation evaluation. +pub(crate) fn resolve_calculated_length_with_context( + calculated: &crate::style_value::StyleValueData, + context: &crate::style_compute::FfiLengthResolutionContext, +) -> Option { + let (value, numeric_type, resolve_as) = resolve_calculated_with_length_resolution( + calculated, + None, + LengthResolution { + context: Some(context), + fallback: None, + }, + )?; + numeric_type.matches_dimension(0, resolve_as).then_some(value) +} + /// The outcome of resolving a line-height calculation: a pixel length or a /// unitless number multiplier. pub(crate) enum ResolvedLineHeightCalc { @@ -2734,22 +3039,28 @@ pub unsafe extern "C" fn rust_calc_resolve( }) } -/// The output surface for serialization: the structure is built here, but -/// every formatted byte comes from the C++ side, so leaf formatting stays -/// identical to the values' own serializers. #[repr(C)] -pub struct FfiCalcSerializationCallbacks { - pub context: *mut std::ffi::c_void, - pub append_literal: unsafe extern "C" fn(context: *mut std::ffi::c_void, bytes: *const u8, length: usize), - /// Appends a numeric leaf: kind follows the numeric dimension order, and - /// the C++ side materializes the value type and runs its serializer. - pub append_numeric_leaf: - unsafe extern "C" fn(context: *mut std::ffi::c_void, kind: u8, value: f64, unit: u8, resolved_mode: bool), - /// Serializes a style value per its own rules, reporting whether any - /// bytes were appended (random()'s sharing options may serialize empty). - pub append_style_value: - unsafe extern "C" fn(context: *mut std::ffi::c_void, shell: *const std::ffi::c_void) -> bool, - pub append_channel_name: unsafe extern "C" fn(context: *mut std::ffi::c_void, channel: u8), +pub struct FfiCalcSerializationPiece { + /// 0 = literal, 1 = numeric leaf, 2 = style value, 3 = channel keyword, + /// 4 = literal conditioned on the preceding piece producing output. + pub kind: u8, + pub numeric_kind: u8, + pub unit_or_channel: u8, + pub value: f64, + pub bytes: *const u8, + pub length: usize, + pub style_value: *const std::ffi::c_void, +} + +#[repr(C)] +pub struct FfiCalcSerialization { + pub pieces: *const FfiCalcSerializationPiece, + pub piece_count: usize, + pub storage: *mut std::ffi::c_void, +} + +struct CalcSerializationStorage { + pieces: Box<[FfiCalcSerializationPiece]>, } impl CalcNumericValue { @@ -2781,22 +3092,63 @@ impl CalcNumericValue { } struct CalcSerializer<'a> { - callbacks: &'a FfiCalcSerializationCallbacks, + pieces: Vec, resolved_mode: bool, resolve_numbers_as_integers: bool, accepted_ranges: &'a [crate::style_value::RetainedNumericRangeByType], } impl CalcSerializer<'_> { - fn literal(&self, text: &str) { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcSerializationCallback); - unsafe { (self.callbacks.append_literal)(self.callbacks.context, text.as_ptr(), text.len()) }; + fn piece(kind: u8) -> FfiCalcSerializationPiece { + FfiCalcSerializationPiece { + kind, + numeric_kind: 0, + unit_or_channel: 0, + value: 0.0, + bytes: std::ptr::null(), + length: 0, + style_value: std::ptr::null(), + } } - fn leaf(&self, value: CalcNumericValue) { + fn literal(&mut self, text: &'static str) { + self.pieces.push(FfiCalcSerializationPiece { + bytes: text.as_ptr(), + length: text.len(), + ..Self::piece(0) + }); + } + + fn conditional_literal(&mut self, text: &'static str) { + self.pieces.push(FfiCalcSerializationPiece { + bytes: text.as_ptr(), + length: text.len(), + ..Self::piece(4) + }); + } + + fn leaf(&mut self, value: CalcNumericValue) { let (kind, raw, unit) = value.leaf_parts(); - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcSerializationCallback); - unsafe { (self.callbacks.append_numeric_leaf)(self.callbacks.context, kind, raw, unit, self.resolved_mode) }; + self.pieces.push(FfiCalcSerializationPiece { + numeric_kind: kind, + unit_or_channel: unit, + value: raw, + ..Self::piece(1) + }); + } + + fn style_value(&mut self, value: &RetainedStyleValueData) { + self.pieces.push(FfiCalcSerializationPiece { + style_value: value.data() as *const _ as *const _, + ..Self::piece(2) + }); + } + + fn channel(&mut self, channel: u8) { + self.pieces.push(FfiCalcSerializationPiece { + unit_or_channel: channel, + ..Self::piece(3) + }); } fn function_name(node: &CalcNode) -> &'static str { @@ -2867,7 +3219,7 @@ impl CalcSerializer<'_> { } /// https://drafts.csswg.org/css-values-4/#serialize-a-math-function - fn serialize_math_function(&self, node: &Arc) { + fn serialize_math_function(&mut self, node: &Arc) { // To serialize a math function fn: // 1. If the root of the calculation tree fn represents is a numeric value (number, @@ -2927,12 +3279,8 @@ impl CalcSerializer<'_> { } = &**node { self.literal("random("); - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcSerializationCallback); - let appended = - unsafe { (self.callbacks.append_style_value)(self.callbacks.context, sharing.shell_pointer()) }; - if appended { - self.literal(", "); - } + self.style_value(sharing); + self.conditional_literal(", "); self.serialize_tree(min, true); self.literal(", "); self.serialize_tree(max, true); @@ -3063,23 +3411,17 @@ impl CalcSerializer<'_> { } /// https://drafts.csswg.org/css-values-4/#serialize-a-calculation-tree - fn serialize_tree(&self, node: &Arc, emit_outer_parentheses: bool) { + fn serialize_tree(&mut self, node: &Arc, emit_outer_parentheses: bool) { // 1. Let root be the root node of the calculation tree. // NOTE: Already the case. match &**node { // 2. If root is a numeric value, or a non-math function, serialize root per the normal // rules for it and return the result. CalcNode::Numeric(value) => self.leaf(*value), - CalcNode::NonMathFunction { value, .. } => { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcSerializationCallback); - unsafe { (self.callbacks.append_style_value)(self.callbacks.context, value.shell_pointer()) }; - } + CalcNode::NonMathFunction { value, .. } => self.style_value(value), // AD-HOC: ChannelKeyword nodes, used for relative-color syntax, serialize directly as // the keyword name. - CalcNode::ChannelKeyword(channel) => unsafe { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcSerializationCallback); - (self.callbacks.append_channel_name)(self.callbacks.context, *channel); - }, + CalcNode::ChannelKeyword(channel) => self.channel(*channel), // 4. If root is a Negate node, let s be a string initially containing "(-1 * ". CalcNode::Negate(child) => { if emit_outer_parentheses { @@ -3181,18 +3523,16 @@ impl CalcSerializer<'_> { } } -/// Serializes a calculated style value's math function through the callback -/// surface. +/// Serializes a calculated style value's math function into an ordered piece batch. Literal +/// structure comes from Rust, while C++ formats numeric and embedded style values after return. /// /// # Safety -/// `calculated` must point at Calculated style value data and `callbacks` at a -/// valid callback table. +/// `calculated` must point at Calculated style value data. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_calc_serialize( calculated: *const std::ffi::c_void, - callbacks: *const FfiCalcSerializationCallbacks, resolved_mode: bool, -) { +) -> FfiCalcSerialization { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcOperationEntry); use crate::style_value::StyleValueData; crate::abort_on_panic(|| { @@ -3206,31 +3546,42 @@ pub unsafe extern "C" fn rust_calc_serialize( unreachable!("rust_calc_serialize requires calculated value data"); }; let root = rust_calculation.node_arc(); - let serializer = CalcSerializer { - callbacks: unsafe { &*callbacks }, + let mut serializer = CalcSerializer { + pieces: Vec::new(), resolved_mode, resolve_numbers_as_integers: *resolve_numbers_as_integers, accepted_ranges: accepted_ranges.as_slice(), }; serializer.serialize_math_function(&root); - }); + let storage = Box::new(CalcSerializationStorage { + pieces: serializer.pieces.into_boxed_slice(), + }); + let result = FfiCalcSerialization { + pieces: storage.pieces.as_ptr(), + piece_count: storage.pieces.len(), + storage: std::ptr::null_mut(), + }; + let storage = Box::into_raw(storage); + FfiCalcSerialization { + storage: storage.cast(), + ..result + } + }) +} + +/// # Safety +/// `storage` must be a value returned by `rust_calc_serialize` that has not been released. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_calc_serialization_release(storage: *mut std::ffi::c_void) { + drop(unsafe { Box::from_raw(storage.cast::()) }); } impl CalcNode { /// Structural equality over two calculation trees, mirroring the C++ node - /// equals implementations: kinds, leaf values and child structures must - /// match, with the style values carried by random() and non-math-function - /// nodes compared through the given value-equality callback. - pub(crate) fn structurally_equals( - &self, - other: &CalcNode, - style_value_equals: &dyn Fn(&RetainedStyleValue, &RetainedStyleValue) -> bool, - ) -> bool { + /// equals implementations: kinds, leaf values and child structures must match. + pub(crate) fn structurally_equals(&self, other: &CalcNode) -> bool { let children_equal = |a: &[Arc], b: &[Arc]| { - a.len() == b.len() - && a.iter() - .zip(b.iter()) - .all(|(a, b)| a.structurally_equals(b, style_value_equals)) + a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| a.structurally_equals(b)) }; match (self, other) { (CalcNode::Numeric(a), CalcNode::Numeric(b)) => a == b, @@ -3251,7 +3602,7 @@ impl CalcNode { | (CalcNode::Acos(a), CalcNode::Acos(b)) | (CalcNode::Atan(a), CalcNode::Atan(b)) | (CalcNode::Sqrt(a), CalcNode::Sqrt(b)) - | (CalcNode::Exp(a), CalcNode::Exp(b)) => a.structurally_equals(b, style_value_equals), + | (CalcNode::Exp(a), CalcNode::Exp(b)) => a.structurally_equals(b), ( CalcNode::Clamp { min: a_min, @@ -3264,9 +3615,9 @@ impl CalcNode { max: b_max, }, ) => { - a_min.structurally_equals(b_min, style_value_equals) - && a_center.structurally_equals(b_center, style_value_equals) - && a_max.structurally_equals(b_max, style_value_equals) + a_min.structurally_equals(b_min) + && a_center.structurally_equals(b_center) + && a_max.structurally_equals(b_max) } ( CalcNode::Progress { @@ -3283,12 +3634,12 @@ impl CalcNode { }, ) => { a_no_clamp == b_no_clamp - && a_progress.structurally_equals(b_progress, style_value_equals) - && a_from.structurally_equals(b_from, style_value_equals) - && a_to.structurally_equals(b_to, style_value_equals) + && a_progress.structurally_equals(b_progress) + && a_from.structurally_equals(b_from) + && a_to.structurally_equals(b_to) } (CalcNode::Atan2 { y: a_y, x: a_x }, CalcNode::Atan2 { y: b_y, x: b_x }) => { - a_y.structurally_equals(b_y, style_value_equals) && a_x.structurally_equals(b_x, style_value_equals) + a_y.structurally_equals(b_y) && a_x.structurally_equals(b_x) } ( CalcNode::Pow { @@ -3299,10 +3650,7 @@ impl CalcNode { base: b_base, exponent: b_exponent, }, - ) => { - a_base.structurally_equals(b_base, style_value_equals) - && a_exponent.structurally_equals(b_exponent, style_value_equals) - } + ) => a_base.structurally_equals(b_base) && a_exponent.structurally_equals(b_exponent), ( CalcNode::Log { value: a_value, @@ -3312,10 +3660,7 @@ impl CalcNode { value: b_value, base: b_base, }, - ) => { - a_value.structurally_equals(b_value, style_value_equals) - && a_base.structurally_equals(b_base, style_value_equals) - } + ) => a_value.structurally_equals(b_value) && a_base.structurally_equals(b_base), ( CalcNode::Round { strategy: a_strategy, @@ -3329,8 +3674,8 @@ impl CalcNode { }, ) => { a_strategy == b_strategy - && a_value.structurally_equals(b_value, style_value_equals) - && a_interval.structurally_equals(b_interval, style_value_equals) + && a_value.structurally_equals(b_value) + && a_interval.structurally_equals(b_interval) } ( CalcNode::Mod { @@ -3341,10 +3686,7 @@ impl CalcNode { value: b_value, modulus: b_modulus, }, - ) => { - a_value.structurally_equals(b_value, style_value_equals) - && a_modulus.structurally_equals(b_modulus, style_value_equals) - } + ) => a_value.structurally_equals(b_value) && a_modulus.structurally_equals(b_modulus), ( CalcNode::Rem { value: a_value, @@ -3354,10 +3696,7 @@ impl CalcNode { value: b_value, divisor: b_divisor, }, - ) => { - a_value.structurally_equals(b_value, style_value_equals) - && a_divisor.structurally_equals(b_divisor, style_value_equals) - } + ) => a_value.structurally_equals(b_value) && a_divisor.structurally_equals(b_divisor), ( CalcNode::Random { min: a_min, @@ -3372,39 +3711,41 @@ impl CalcNode { sharing: b_sharing, }, ) => { - a_min.structurally_equals(b_min, style_value_equals) - && a_max.structurally_equals(b_max, style_value_equals) + a_min.structurally_equals(b_min) + && a_max.structurally_equals(b_max) && match (a_step, b_step) { (None, None) => true, - (Some(a), Some(b)) => a.structurally_equals(b, style_value_equals), + (Some(a), Some(b)) => a.structurally_equals(b), _ => false, } - && style_value_equals(a_sharing, b_sharing) + && a_sharing == b_sharing } (CalcNode::NonMathFunction { value: a_value, .. }, CalcNode::NonMathFunction { value: b_value, .. }) => { - style_value_equals(a_value, b_value) + a_value == b_value } _ => false, } } + + fn contains_anchor_function(&self) -> bool { + if let CalcNode::NonMathFunction { value, .. } = self + && matches!(value.data(), crate::style_value::StyleValueData::Anchor { .. }) + { + return true; + } + + let mut contains_anchor = false; + self.for_each_child(&mut |child| contains_anchor |= child.contains_anchor_function()); + contains_anchor + } } /// Structural equality of two calculated style values' trees. /// /// # Safety -/// Both pointers must reference Calculated style value data, and the callback -/// must be valid. +/// Both pointers must reference Calculated style value data. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_calc_equals( - first: *const std::ffi::c_void, - second: *const std::ffi::c_void, - context: *mut std::ffi::c_void, - style_value_equals: unsafe extern "C" fn( - context: *mut std::ffi::c_void, - a: *const std::ffi::c_void, - b: *const std::ffi::c_void, - ) -> bool, -) -> bool { +pub unsafe extern "C" fn rust_calc_equals(first: *const std::ffi::c_void, second: *const std::ffi::c_void) -> bool { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcOperationEntry); use crate::style_value::StyleValueData; crate::abort_on_panic(|| { @@ -3417,14 +3758,28 @@ pub unsafe extern "C" fn rust_calc_equals( }; let first_tree = tree_of(first); let second_tree = tree_of(second); - first_tree.structurally_equals(&second_tree, &|a, b| unsafe { - style_value_equals(context, a.shell_pointer(), b.shell_pointer()) - }) + first_tree.structurally_equals(&second_tree) }) } -/// The node kind codes exposed to the C++ read API, in a stable documented -/// order for the reification walk. +/// Whether a calculated style value contains an anchor() function. +/// +/// # Safety +/// `calculated` must point at Calculated style value data. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_calc_contains_anchor(calculated: *const std::ffi::c_void) -> bool { + crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcOperationEntry); + crate::abort_on_panic(|| { + let crate::style_value::StyleValueData::Calculated { rust_calculation, .. } = + (unsafe { &*(calculated as *const crate::style_value::StyleValueData) }) + else { + unreachable!("rust_calc_contains_anchor requires calculated value data"); + }; + rust_calculation.node().contains_anchor_function() + }) +} + +/// The node kind codes exposed to C++ in a stable documented order. fn node_kind_code(node: &CalcNode) -> u8 { match node { CalcNode::Numeric(..) => 0, @@ -3459,62 +3814,153 @@ fn node_kind_code(node: &CalcNode) -> u8 { } } -/// # Safety -/// `node` must be a valid calculation node pointer for all read functions. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_calc_node_kind(node: *const CalcNode) -> u8 { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcNodeQueryEntry); - crate::abort_on_panic(|| node_kind_code(unsafe { &*node })) +#[repr(C)] +pub struct FfiCalcReificationNode { + pub kind: u8, + pub numeric_kind: u8, + pub unit: u8, + pub value: f64, + pub numeric_type: FfiNumericType, + pub child_start: usize, + pub child_count: usize, } -/// The node's calculation children in their canonical order. Returned -/// pointers are borrowed from the node's own references. +#[repr(C)] +pub struct FfiCalcReification { + pub nodes: *const FfiCalcReificationNode, + pub node_count: usize, + pub children: *const usize, + pub child_count: usize, + pub storage: *mut std::ffi::c_void, +} + +struct CalcReificationStorage { + nodes: Box<[FfiCalcReificationNode]>, + children: Box<[usize]>, +} + +fn append_reification_node( + node: &CalcNode, + percentage_leaf_type: &CalcNumericType, + nodes: &mut Vec, + children: &mut Vec, +) -> Option { + if !matches!( + node, + CalcNode::Numeric(..) + | CalcNode::Sum(..) + | CalcNode::Product(..) + | CalcNode::Negate(..) + | CalcNode::Invert(..) + | CalcNode::Min(..) + | CalcNode::Max(..) + | CalcNode::Clamp { .. } + ) { + return None; + } + + let mut child_indices = Vec::new(); + let mut failed = false; + node.for_each_child(&mut |child| { + if let Some(index) = append_reification_node(child, percentage_leaf_type, nodes, children) { + child_indices.push(index); + } else { + failed = true; + } + }); + if failed { + return None; + } + + let child_start = children.len(); + children.extend(child_indices); + let (numeric_kind, value, unit) = match node { + CalcNode::Numeric(value) => value.leaf_parts(), + _ => (0, 0.0, 0), + }; + let index = nodes.len(); + nodes.push(FfiCalcReificationNode { + kind: node_kind_code(node), + numeric_kind, + unit, + value, + numeric_type: FfiNumericType::from_calc(node.numeric_type(percentage_leaf_type)), + child_start, + child_count: children.len() - child_start, + }); + Some(index) +} + +/// https://drafts.css-houdini.org/css-typed-om-1/#reify-a-math-expression +/// Describe a calculation tree in postorder so C++ can create its GC-owned Typed OM objects. +/// Unsupported math functions return an empty description. /// /// # Safety -/// `out_children` must have room for `capacity` entries. +/// `calculated` must point at Calculated style value data. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_calc_node_children( - node: *const CalcNode, - out_children: *mut *const CalcNode, - capacity: usize, -) -> usize { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcNodeQueryEntry); +pub unsafe extern "C" fn rust_calc_describe_for_typed_om(calculated: *const std::ffi::c_void) -> FfiCalcReification { + crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcOperationEntry); crate::abort_on_panic(|| { - let mut count = 0; - unsafe { &*node }.for_each_child(&mut |child| { - if count < capacity { - unsafe { *out_children.add(count) = Arc::as_ptr(child) }; - } - count += 1; + let crate::style_value::StyleValueData::Calculated { + rust_calculation, + has_percentages_resolve_as, + resolve_as_is_number, + resolve_as_base, + .. + } = (unsafe { &*(calculated as *const crate::style_value::StyleValueData) }) + else { + unreachable!("rust_calc_describe_for_typed_om requires calculated value data"); + }; + let percentage_leaf_type = percentage_leaf_type_for(resolve_as_from_fields( + *has_percentages_resolve_as, + *resolve_as_is_number, + *resolve_as_base, + )); + let mut nodes = Vec::new(); + let mut children = Vec::new(); + if append_reification_node( + rust_calculation.node(), + &percentage_leaf_type, + &mut nodes, + &mut children, + ) + .is_none() + { + return FfiCalcReification { + nodes: std::ptr::null(), + node_count: 0, + children: std::ptr::null(), + child_count: 0, + storage: std::ptr::null_mut(), + }; + } + let storage = Box::new(CalcReificationStorage { + nodes: nodes.into_boxed_slice(), + children: children.into_boxed_slice(), }); - count + let result = FfiCalcReification { + nodes: storage.nodes.as_ptr(), + node_count: storage.nodes.len(), + children: storage.children.as_ptr(), + child_count: storage.children.len(), + storage: std::ptr::null_mut(), + }; + let storage = Box::into_raw(storage); + FfiCalcReification { + storage: storage.cast(), + ..result + } }) } -/// Reads a numeric leaf's parts; false for non-leaf nodes. -/// /// # Safety -/// All out-pointers must be valid. +/// `storage` must be null or a value returned by `rust_calc_describe_for_typed_om` that has not +/// already been released. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_calc_node_numeric_leaf( - node: *const CalcNode, - out_kind: *mut u8, - out_value: *mut f64, - out_unit: *mut u8, -) -> bool { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcNodeQueryEntry); - crate::abort_on_panic(|| { - let CalcNode::Numeric(value) = (unsafe { &*node }) else { - return false; - }; - let (kind, raw, unit) = value.leaf_parts(); - unsafe { - *out_kind = kind; - *out_value = raw; - *out_unit = unit; - } - true - }) +pub unsafe extern "C" fn rust_calc_reification_release(storage: *mut std::ffi::c_void) { + if !storage.is_null() { + drop(unsafe { Box::from_raw(storage.cast::()) }); + } } /// The style value carried by a random() or non-math-function node @@ -3526,45 +3972,12 @@ pub unsafe extern "C" fn rust_calc_node_numeric_leaf( pub unsafe extern "C" fn rust_calc_node_style_value(node: *const CalcNode) -> *const std::ffi::c_void { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcNodeQueryEntry); crate::abort_on_panic(|| match unsafe { &*node } { - CalcNode::Random { sharing, .. } => sharing.shell_pointer(), - CalcNode::NonMathFunction { value, .. } => value.shell_pointer(), + CalcNode::Random { sharing, .. } => sharing.data() as *const _ as *const _, + CalcNode::NonMathFunction { value, .. } => value.data() as *const _ as *const _, _ => std::ptr::null(), }) } -/// The numeric type of a node within a calculated value's context, for the -/// reification of math function objects. -/// -/// # Safety -/// `calculated` must point at Calculated style value data owning `node`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_calc_node_numeric_type( - calculated: *const std::ffi::c_void, - node: *const CalcNode, -) -> FfiNumericType { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CalcNodeQueryEntry); - use crate::style_value::StyleValueData; - crate::abort_on_panic(|| { - let StyleValueData::Calculated { - has_percentages_resolve_as, - resolve_as_is_number, - resolve_as_base, - .. - } = (unsafe { &*(calculated as *const StyleValueData) }) - else { - unreachable!("rust_calc_node_numeric_type requires calculated value data"); - }; - let mut percentage_leaf_type = CalcNumericType::default(); - if *has_percentages_resolve_as && !*resolve_as_is_number { - percentage_leaf_type.exponents[*resolve_as_base as usize] = Some(1); - percentage_leaf_type.percent_hint = Some(*resolve_as_base); - } else { - percentage_leaf_type.exponents[BASE_TYPE_PERCENT] = Some(1); - } - FfiNumericType::from_calc(unsafe { &*node }.numeric_type(&percentage_leaf_type)) - }) -} - /// Simplifies a free-standing calculation tree: the css-values-4 algorithm /// over a borrowed root, returning the simplified tree as a transferred /// handle. This backs the C++ simplify_a_calculation_tree entry, whose diff --git a/Libraries/LibWeb/CSS/Rust/src/cascaded_properties.rs b/Libraries/LibWeb/CSS/Rust/src/cascaded_properties.rs index f8e8ae38b5020..e999652bdca2f 100644 --- a/Libraries/LibWeb/CSS/Rust/src/cascaded_properties.rs +++ b/Libraries/LibWeb/CSS/Rust/src/cascaded_properties.rs @@ -8,7 +8,7 @@ //! cascade, one winning declaration list per longhand. //! //! This is the Rust backing for the C++ CascadedProperties shell. Entries own -//! strong references to their C++ StyleValue shells and layer name strings. +//! strong references to Rust-owned style value data and layer name strings. //! The GC-managed declaration sources stay on the C++ side, pinned in a slot //! table of weak references; each entry carries its slot index and the C++ //! shell resolves a slot back to the source objects on demand. @@ -21,7 +21,7 @@ use std::hash::Hasher; use crate::abort_on_panic; use crate::property_metadata::LAST_LONGHAND_PROPERTY_ID; use crate::style_compute::expand_shorthands_with; -use crate::style_value::RetainedStyleValue; +use crate::style_value::RetainedStyleValueData; use crate::style_value::RetainedUtf16FlyString; use crate::style_value::StyleValueData; @@ -61,10 +61,8 @@ impl LayerName { } struct Entry { - value: RetainedStyleValue, - /// The Rust-owned data of `value`, recorded so the property computation - /// driver can read winning declarations without any shell interaction. - value_data: *const c_void, + value: RetainedStyleValueData, + has_style_sheet_context: bool, important: bool, cascade_index: u64, origin: CascadeOrigin, @@ -154,8 +152,8 @@ impl CascadedPropertyStore { fn set_property( &mut self, property_id: u16, - value: RetainedStyleValue, - value_data: *const c_void, + value: RetainedStyleValueData, + has_style_sheet_context: bool, important: bool, origin: CascadeOrigin, layer_name: LayerName, @@ -176,7 +174,7 @@ impl CascadedPropertyStore { return -1; } entry.value = value; - entry.value_data = value_data; + entry.has_style_sheet_context = has_style_sheet_context; entry.important = important; entry.cascade_index = cascade_index; return entry.source_slot as i64; @@ -186,7 +184,7 @@ impl CascadedPropertyStore { let source_slot = self.allocate_source_slot(); self.entries.get_mut(&property_id).unwrap().push(Entry { value, - value_data, + has_style_sheet_context, important, cascade_index, origin, @@ -197,11 +195,17 @@ impl CascadedPropertyStore { source_slot as i64 } - /// The winning declaration for a property: its shell pointer, Rust-owned - /// data, and importance. - pub(crate) fn winning_declaration(&self, property_id: u16) -> Option<(*const c_void, *const c_void, bool)> { - self.last_entry(property_id) - .map(|entry| (entry.value.shell_pointer(), entry.value_data, entry.important)) + /// The winning declaration for a property: its Rust-owned data, importance, + /// and C++ declaration-source slot. + pub(crate) fn winning_declaration(&self, property_id: u16) -> Option<(*const c_void, bool, u32, bool)> { + self.last_entry(property_id).map(|entry| { + ( + entry.value.pointer().cast(), + entry.important, + entry.source_slot, + entry.has_style_sheet_context, + ) + }) } /// Returns whichever of the two properties has the higher-priority winning @@ -285,7 +289,7 @@ pub unsafe extern "C" fn rust_cascaded_properties_destroy(store: *mut CascadedPr abort_on_panic(|| drop(unsafe { Box::from_raw(store) })); } -/// Returns a borrowed pointer to the winning declaration's StyleValue shell, or null. +/// Returns a borrowed pointer to the winning declaration's Rust-owned style value data, or null. /// /// # Safety /// `store` must be a valid store. @@ -296,7 +300,7 @@ pub unsafe extern "C" fn rust_cascaded_properties_property( ) -> *const c_void { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadedStoreQueryEntry); abort_on_panic(|| match unsafe { &*store }.last_entry(property_id) { - Some(entry) => entry.value.shell_pointer(), + Some(entry) => entry.value.pointer().cast(), None => std::ptr::null(), }) } @@ -317,14 +321,31 @@ pub unsafe extern "C" fn rust_cascaded_properties_source_slot( }) } +/// Returns whether the winning declaration's original C++ facade carried +/// stylesheet resource context. +/// +/// # Safety +/// `store` must be a valid store. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_cascaded_properties_has_style_sheet_context( + store: *const CascadedPropertyStore, + property_id: u16, +) -> bool { + crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadedStoreQueryEntry); + abort_on_panic(|| { + unsafe { &*store } + .last_entry(property_id) + .is_some_and(|entry| entry.has_style_sheet_context) + }) +} + /// A declared property in an `FfiCascadeBlock` crossing into `rust_cascade_matched_blocks`: -/// the property identifier, its importance, and the borrowed value shell with its -/// Rust-owned data. +/// the property identifier, its importance, and borrowed shared Rust value data. #[repr(C)] pub struct FfiCascadeDeclaration { pub property_id: u16, pub important: bool, - pub shell: *const c_void, + pub has_style_sheet_context: bool, pub data: *const c_void, } @@ -335,7 +356,7 @@ pub struct FfiCustomPropertyDeclaration { pub name_raw: usize, pub important: bool, pub is_revert_layer: bool, - pub shell: *const c_void, + pub data: *const c_void, } /// Applies one declaration block to the cascade: filters by importance and applicability, @@ -351,15 +372,12 @@ fn apply_declaration_block( has_layer_name: bool, layer_name_raw: usize, source_shadow_root_identity: usize, - unset_shell: *const c_void, unset_data: *const c_void, is_property_disallowed: &dyn Fn(u16) -> bool, resolve_unresolved: &dyn Fn(u16, *const c_void) -> FfiResolvedStyleValue, parse_substituted: &dyn Fn(u16, &[u8]) -> FfiResolvedStyleValue, custom_property_store: *const c_void, custom_property_registry: *const c_void, - data_of: &dyn Fn(*const c_void) -> *const c_void, - create_pending_substitution: &dyn Fn(*const c_void) -> *const c_void, mut assign_source_slot: impl FnMut(u32), ) { let mut seen = [0u64; CONTAINED_BITMAP_WORDS]; @@ -380,8 +398,8 @@ fn apply_declaration_block( continue; } - let mut shell = declaration.shell; let mut data = declaration.data; + let mut has_style_sheet_context = declaration.has_style_sheet_context; if declared_is_unresolved { let native_resolution = unsafe { @@ -390,18 +408,18 @@ fn apply_declaration_block( match native_resolution { crate::custom_properties::NativeVarResolution::Resolved(source) => { let resolved = parse_substituted(declaration.property_id, &source); - shell = resolved.shell; data = resolved.data; + has_style_sheet_context = resolved.has_style_sheet_context; } crate::custom_properties::NativeVarResolution::Invalid => { let resolved = parse_substituted(declaration.property_id, &[]); - shell = resolved.shell; data = resolved.data; + has_style_sheet_context = resolved.has_style_sheet_context; } crate::custom_properties::NativeVarResolution::NotHandled => { - let resolved = resolve_unresolved(declaration.property_id, shell); - shell = resolved.shell; + let resolved = resolve_unresolved(declaration.property_id, data); data = resolved.data; + has_style_sheet_context = resolved.has_style_sheet_context; } } } @@ -423,22 +441,19 @@ fn apply_declaration_block( // -> Otherwise // Either the property's inherited value or its initial value depending on whether the property is // inherited or not, respectively, as if the property's value had been specified as the unset keyword. - shell = unset_shell; data = unset_data; + has_style_sheet_context = false; } let value_is_pending_substitution = matches!( unsafe { &*(data as *const StyleValueData) }, StyleValueData::PendingSubstitution { .. } ); - expand_shorthands_with( - &|shell| data_of(shell), - &|shell| create_pending_substitution(shell), declaration.property_id, - shell, data, - &mut |longhand_id, longhand_shell, longhand_data| { + has_style_sheet_context, + &mut |longhand_id, longhand_data, longhand_has_style_sheet_context| { if is_property_disallowed(longhand_id) { return; } @@ -472,14 +487,18 @@ fn apply_declaration_block( // Track the exact shadow-root scope that supplied this winning declaration. A constructable // stylesheet can be adopted into multiple scopes at once, so the declaration object alone is // not specific enough. - let retained_value = unsafe { RetainedStyleValue::from_borrowed_shell_pointer(longhand_shell) }; + let retained_value = unsafe { + RetainedStyleValueData::from_retained_pointer(crate::style_value::rust_style_value_retain( + longhand_data.cast(), + )) + }; let layer_name = LayerName( has_layer_name.then(|| unsafe { RetainedUtf16FlyString::from_borrowed_raw(layer_name_raw) }), ); let slot = store.set_property( longhand_id, retained_value, - longhand_data, + longhand_has_style_sheet_context, important, origin, layer_name, @@ -534,32 +553,25 @@ pub struct FfiSourceSlotAssignment { pub struct FfiCascadedCustomProperty { pub name_raw: usize, pub important: bool, - pub shell: *const c_void, + pub data: *const c_void, } -/// Callbacks for the bulk cascade. Values cross as opaque C++ style value -/// shells; the C++ side pins every value it creates until the cascade -/// returns. +/// Callbacks for the parser-dependent parts of the bulk cascade. Values cross +/// as shared Rust data; the C++ side pins every value it creates until the +/// cascade returns. #[repr(C)] pub struct FfiBulkCascadeCallbacks { pub context: *mut c_void, - /// Resolves an unresolved value and returns its pinned shell and Rust-owned data. + /// Resolves borrowed Rust value data and returns pinned Rust-owned data. pub resolve_unresolved: - unsafe extern "C" fn(context: *mut c_void, property_id: u16, shell: *const c_void) -> FfiResolvedStyleValue, - /// Parses a substituted token stream and returns its pinned shell and Rust-owned data. + unsafe extern "C" fn(context: *mut c_void, property_id: u16, data: *const c_void) -> FfiResolvedStyleValue, + /// Parses a substituted token stream and returns pinned Rust-owned data. pub parse_substituted: unsafe extern "C" fn( context: *mut c_void, property_id: u16, source: *const u8, source_length: usize, ) -> FfiResolvedStyleValue, - /// Returns the Rust-owned data of a C++ style value shell. - pub data_of: unsafe extern "C" fn(context: *mut c_void, shell: *const c_void) -> *const c_void, - /// Creates and pins a pending-substitution value wrapping the given value; returns its shell. - pub create_pending_substitution: unsafe extern "C" fn(context: *mut c_void, shell: *const c_void) -> *const c_void, - /// Whether the element's pseudo-element rejects the property; only called - /// when the element has a pseudo-element. - pub pseudo_element_rejects_property: unsafe extern "C" fn(context: *mut c_void, property_id: u16) -> bool, /// Receives every winning slot's source assignment in one batch. pub assign_source_slots: unsafe extern "C" fn(context: *mut c_void, assignments: *const FfiSourceSlotAssignment, count: usize), @@ -573,10 +585,13 @@ pub struct FfiBulkCascadeCallbacks { #[repr(C)] pub struct FfiResolvedStyleValue { - pub shell: *const c_void, pub data: *const c_void, + pub has_style_sheet_context: bool, } +/// Sentinel passed when cascading for an element rather than a pseudo-element. +pub(crate) const NO_PSEUDO_ELEMENT: u8 = u8::MAX; + /// Runs the whole cascade for one element in css-cascade-5 origin order over /// the matched declaration blocks: /// @@ -600,10 +615,8 @@ pub unsafe extern "C" fn rust_cascade_matched_blocks( blocks: *const FfiCascadeBlock, block_count: usize, author_context_count: u32, - has_pseudo_element: bool, - cascade_custom_properties: bool, + pseudo_element: u8, custom_property_registry: *const c_void, - unset_shell: *const c_void, unset_data: *const c_void, callbacks: *const FfiBulkCascadeCallbacks, ) { @@ -696,6 +709,12 @@ pub unsafe extern "C" fn rust_cascade_matched_blocks( application_order.push((index, true, false)); } + let has_pseudo_element = pseudo_element != NO_PSEUDO_ELEMENT; + let cascade_custom_properties = !has_pseudo_element + || crate::property_metadata::pseudo_element_supports_property( + pseudo_element, + crate::property_metadata::property_id::CUSTOM, + ); let mut custom_property_store = std::ptr::null(); if cascade_custom_properties { let mut custom_property_indices = HashMap::new(); @@ -719,7 +738,7 @@ pub unsafe extern "C" fn rust_cascade_matched_blocks( let property = FfiCascadedCustomProperty { name_raw: declaration.name_raw, important, - shell: declaration.shell, + data: declaration.data, }; if let Some(index) = custom_property_indices.get(&declaration.name_raw) { custom_properties[*index] = property; @@ -749,8 +768,7 @@ pub unsafe extern "C" fn rust_cascade_matched_blocks( if block.bypass_pseudo_element_property_whitelist || !has_pseudo_element { return false; } - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadePropertyDisallowedCallback); - unsafe { (callbacks.pseudo_element_rejects_property)(context, property_id) } + !crate::property_metadata::pseudo_element_supports_property(pseudo_element, property_id) }; apply_declaration_block( store, @@ -760,12 +778,11 @@ pub unsafe extern "C" fn rust_cascade_matched_blocks( use_layer_name && block.has_layer_name, block.layer_name_raw, block.source_shadow_root_identity, - unset_shell, unset_data, &is_property_disallowed, - &|property_id, shell| { + &|property_id, data| { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadeResolveUnresolvedCallback); - unsafe { (callbacks.resolve_unresolved)(context, property_id, shell) } + unsafe { (callbacks.resolve_unresolved)(context, property_id, data) } }, &|property_id, source| { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadeParseSubstitutedCallback); @@ -773,14 +790,6 @@ pub unsafe extern "C" fn rust_cascade_matched_blocks( }, custom_property_store, custom_property_registry, - &|shell| { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadeDataOfCallback); - unsafe { (callbacks.data_of)(context, shell) } - }, - &|shell| { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadePendingSubstitutionCallback); - unsafe { (callbacks.create_pending_substitution)(context, shell) } - }, |slot| { source_slot_assignments.push(FfiSourceSlotAssignment { slot, @@ -806,3 +815,43 @@ pub unsafe extern "C" fn rust_cascade_matched_blocks( } }); } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use super::*; + + #[test] + fn winning_declaration_retains_rust_value_data() { + let source_value = Arc::new(StyleValueData::Number { value: 42.0 }); + let weak_value = Arc::downgrade(&source_value); + let retained_value = unsafe { RetainedStyleValueData::from_retained_pointer(Arc::into_raw(source_value)) }; + let mut store = CascadedPropertyStore::new(); + + store.set_property( + crate::property_metadata::property_id::OPACITY, + retained_value, + false, + false, + CascadeOrigin::Author, + LayerName(None), + 0, + ); + + let (data, important, source_slot, has_style_sheet_context) = store + .winning_declaration(crate::property_metadata::property_id::OPACITY) + .expect("the declaration must be retained"); + assert!(!important); + assert_eq!(source_slot, 0); + assert!(!has_style_sheet_context); + assert!(matches!( + unsafe { &*(data as *const StyleValueData) }, + StyleValueData::Number { value } if *value == 42.0 + )); + assert!(weak_value.upgrade().is_some()); + + drop(store); + assert!(weak_value.upgrade().is_none()); + } +} diff --git a/Libraries/LibWeb/CSS/Rust/src/color_conversion.rs b/Libraries/LibWeb/CSS/Rust/src/color_conversion.rs new file mode 100644 index 0000000000000..e3ff30fc5bc15 --- /dev/null +++ b/Libraries/LibWeb/CSS/Rust/src/color_conversion.rs @@ -0,0 +1,562 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +//! Color-space conversions used by CSS color interpolation. + +pub(crate) type Components = [f32; 4]; + +pub(crate) const RGB: u8 = 0; +pub(crate) const A98_RGB: u8 = 1; +pub(crate) const DISPLAY_P3: u8 = 2; +pub(crate) const DISPLAY_P3_LINEAR: u8 = 3; +pub(crate) const HSL: u8 = 4; +pub(crate) const HWB: u8 = 5; +pub(crate) const LAB: u8 = 6; +pub(crate) const LCH: u8 = 7; +pub(crate) const OKLAB: u8 = 8; +pub(crate) const OKLCH: u8 = 9; +pub(crate) const SRGB: u8 = 10; +pub(crate) const SRGB_LINEAR: u8 = 11; +pub(crate) const PROPHOTO_RGB: u8 = 12; +pub(crate) const REC2020: u8 = 13; +pub(crate) const XYZ_D50: u8 = 14; +pub(crate) const XYZ_D65: u8 = 15; + +// https://drafts.csswg.org/css-color-4/#predefined-sRGB +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn srgb_to_linear_srgb(srgb: Components) -> Components { + let convert = |component: f32| { + let sign = if component < 0.0 { -1.0 } else { 1.0 }; + let absolute = component.abs(); + if absolute <= 0.04045 { + component / 12.92 + } else { + sign * ((absolute + 0.055) / 1.055).powf(2.4) + } + }; + [convert(srgb[0]), convert(srgb[1]), convert(srgb[2]), srgb[3]] +} + +// https://drafts.csswg.org/css-color-4/#predefined-sRGB +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn linear_srgb_to_srgb(linear: Components) -> Components { + let convert = |component: f32| { + let sign = if component < 0.0 { -1.0 } else { 1.0 }; + let absolute = component.abs(); + if absolute > 0.0031308 { + sign * (1.055 * absolute.powf(1.0 / 2.4) - 0.055) + } else { + 12.92 * component + } + }; + [convert(linear[0]), convert(linear[1]), convert(linear[2]), linear[3]] +} + +// https://drafts.csswg.org/css-color-4/#predefined-sRGB-linear +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn linear_srgb_to_xyz65(color: Components) -> Components { + let [red, green, blue, alpha] = color; + [ + 0.4123908 * red + 0.35758433 * green + 0.1804808 * blue, + 0.212639 * red + 0.71516865 * green + 0.07219232 * blue, + 0.01933082 * red + 0.11919478 * green + 0.95053214 * blue, + alpha, + ] +} + +// https://drafts.csswg.org/css-color-4/#predefined-display-p3 +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn display_p3_to_xyz65(color: Components, is_linear: bool) -> Components { + let convert = |component: f32| { + if is_linear { + return component; + } + let sign = if component < 0.0 { -1.0 } else { 1.0 }; + let absolute = component.abs(); + if absolute <= 0.04045 { + component / 12.92 + } else { + sign * ((absolute + 0.055) / 1.055).powf(2.4) + } + }; + let red = convert(color[0]); + let green = convert(color[1]); + let blue = convert(color[2]); + [ + 0.48657095 * red + 0.2656677 * green + 0.19821729 * blue, + 0.22897457 * red + 0.69173855 * green + 0.07928691 * blue, + 0.04511338 * green + 1.0439444 * blue, + color[3], + ] +} + +// https://drafts.csswg.org/css-color-4/#predefined-a98-rgb +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn a98_rgb_to_xyz65(color: Components) -> Components { + let convert = |component: f32| component.signum() * component.abs().powf(563.0 / 256.0); + let red = convert(color[0]); + let green = convert(color[1]); + let blue = convert(color[2]); + [ + 0.57666904 * red + 0.18555824 * green + 0.18822865 * blue, + 0.29734498 * red + 0.62736356 * green + 0.07529146 * blue, + 0.02703136 * red + 0.07068885 * green + 0.99133754 * blue, + color[3], + ] +} + +// https://drafts.csswg.org/css-color-4/#predefined-prophoto-rgb +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn prophoto_rgb_to_xyz50(color: Components) -> Components { + let convert = |component: f32| { + let absolute = component.abs(); + if absolute <= 16.0 / 512.0 { + component / 16.0 + } else { + component.signum() * absolute.powf(1.8) + } + }; + let red = convert(color[0]); + let green = convert(color[1]); + let blue = convert(color[2]); + [ + 0.7977666 * red + 0.1351813 * green + 0.03134773 * blue, + 0.28807482 * red + 0.7118352 * green + 0.00008994 * blue, + 0.8251046 * blue, + color[3], + ] +} + +// https://drafts.csswg.org/css-color-4/#predefined-rec2020 +fn rec2020_to_xyz65(color: Components) -> Components { + let convert = |component: f32| { + // AD-HOC: CSS Color 4 specifies a pure 2.4 gamma transfer function for rec2020, but all major engines + // and the WPT rec2020 reftests use the piecewise OETF from ITU-R BT.2020-2, so we do the same. + const ALPHA: f32 = 1.0992968; + const BETA: f32 = 0.01805397; + let absolute = component.abs(); + if absolute < BETA * 4.5 { + component / 4.5 + } else { + component.signum() * ((absolute + ALPHA - 1.0) / ALPHA).powf(1.0 / 0.45) + } + }; + let red = convert(color[0]); + let green = convert(color[1]); + let blue = convert(color[2]); + [ + 0.63695806 * red + 0.1446169 * green + 0.16888098 * blue, + 0.2627002 * red + 0.67799807 * green + 0.05930172 * blue, + 0.02807269 * green + 1.0609851 * blue, + color[3], + ] +} + +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn xyz50_to_xyz65(color: Components) -> Components { + let [x, y, z, alpha] = color; + [ + 0.9554735 * x - 0.02309854 * y + 0.06325931 * z, + -0.02836971 * x + 1.0099955 * y + 0.02104154 * z, + 0.012314 * x - 0.0205077 * y + 1.3303659 * z, + alpha, + ] +} + +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn xyz65_to_xyz50(color: Components) -> Components { + let [x, y, z, alpha] = color; + [ + 1.0479298 * x + 0.022946874 * y - 0.05019223 * z, + 0.029627815 * x + 0.99043447 * y - 0.017073825 * z, + -0.009243058 * x + 0.015055145 * y + 0.75187427 * z, + alpha, + ] +} + +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn lab_to_xyz50(color: Components) -> Components { + const KAPPA: f32 = 24389.0 / 27.0; + const EPSILON: f32 = 216.0 / 24389.0; + let [lightness, a, b, alpha] = color; + let f1 = (lightness + 16.0) / 116.0; + let f0 = a / 500.0 + f1; + let f2 = f1 - b / 200.0; + let compute = |f: f32| { + let cubed = f * f * f; + if cubed > EPSILON { + cubed + } else { + (116.0 * f - 16.0) / KAPPA + } + }; + let y = if lightness > KAPPA * EPSILON { + ((lightness + 16.0) / 116.0).powi(3) + } else { + lightness / KAPPA + }; + let x_n = 0.3457 / 0.3585; + let z_n = (1.0 - 0.3457 - 0.3585) / 0.3585; + [x_n * compute(f0), y, z_n * compute(f2), alpha] +} + +fn polar_to_rectangular(color: Components) -> Components { + let radians = color[2].to_radians(); + [color[0], color[1] * radians.cos(), color[1] * radians.sin(), color[3]] +} + +fn rectangular_to_polar(color: Components) -> Components { + let chroma = color[1].hypot(color[2]); + let mut hue = color[2].atan2(color[1]).to_degrees(); + if hue < 0.0 { + hue += 360.0; + } + [color[0], chroma, hue, color[3]] +} + +// Algorithm from https://drafts.csswg.org/css-color-3/#hsl-color +fn hsl_to_srgb(color: Components) -> Components { + let mut hue = color[0] % 360.0; + if hue < 0.0 { + hue += 360.0; + } + let convert = |offset: f32| { + let k = (offset + hue / 30.0) % 12.0; + let a = color[1] * color[2].min(1.0 - color[2]); + color[2] - a * (-1.0_f32).max((k - 3.0).min(9.0 - k).min(1.0)) + }; + [convert(0.0), convert(8.0), convert(4.0), color[3].clamp(0.0, 1.0)] +} + +// https://drafts.csswg.org/css-color-4/#hwb-to-rgb +fn hwb_to_srgb(color: Components) -> Components { + if color[1] + color[2] >= 1.0 { + let gray = color[1] / (color[1] + color[2]); + return [gray, gray, gray, color[3]]; + } + let rgb = hsl_to_srgb([color[0], 1.0, 0.5, color[3]]); + let scale = 1.0 - color[1] - color[2]; + [ + rgb[0] * scale + color[1], + rgb[1] * scale + color[1], + rgb[2] * scale + color[1], + color[3], + ] +} + +// https://drafts.csswg.org/css-color-4/#rgb-to-hsl +fn srgb_to_hsl(color: Components) -> Components { + let [red, green, blue, alpha] = color; + let maximum = red.max(green).max(blue); + let minimum = red.min(green).min(blue); + let chroma = maximum - minimum; + let lightness = (minimum + maximum) / 2.0; + let mut hue = 0.0; + let mut saturation = 0.0; + + if chroma != 0.0 { + if lightness != 0.0 && lightness != 1.0 { + saturation = (maximum - lightness) / lightness.min(1.0 - lightness); + } + if maximum == red { + hue = (green - blue) / chroma + if green < blue { 6.0 } else { 0.0 }; + } else if maximum == green { + hue = (blue - red) / chroma + 2.0; + } else { + hue = (red - green) / chroma + 4.0; + } + hue *= 60.0; + if saturation < 0.0 { + hue += 180.0; + saturation = saturation.abs(); + } + if hue >= 360.0 { + hue -= 360.0; + } + } + [hue, saturation, lightness, alpha] +} + +// https://drafts.csswg.org/css-color-4/#rgb-to-hwb +fn srgb_to_hwb(color: Components) -> Components { + let [red, green, blue, alpha] = color; + let maximum = red.max(green).max(blue); + let minimum = red.min(green).min(blue); + let chroma = maximum - minimum; + let mut hue = 0.0; + if chroma != 0.0 { + if maximum == red { + hue = (green - blue) / chroma + if green < blue { 6.0 } else { 0.0 }; + } else if maximum == green { + hue = (blue - red) / chroma + 2.0; + } else { + hue = (red - green) / chroma + 4.0; + } + hue *= 60.0; + if hue >= 360.0 { + hue -= 360.0; + } + } + [hue, minimum, 1.0 - maximum, alpha] +} + +fn xyz65_to_linear_srgb(color: Components) -> Components { + let [x, y, z, alpha] = color; + [ + 3.240_97 * x - 1.537383 * y - 0.498611 * z, + -0.969244 * x + 1.875968 * y + 0.041555 * z, + 0.055630 * x - 0.203977 * y + 1.056972 * z, + alpha, + ] +} + +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn oklab_to_xyz65(color: Components) -> Components { + let [lightness, a, b, alpha] = color; + let long = (lightness + 0.39633778 * a + 0.21580376 * b).powi(3); + let medium = (lightness - 0.105561346 * a - 0.06385417 * b).powi(3); + let short = (lightness - 0.08948418 * a - 1.2914855 * b).powi(3); + [ + 1.2268798 * long - 0.557815 * medium + 0.28139105 * short, + -0.040575746 * long + 1.1122868 * medium - 0.071711056 * short, + -0.07637294 * long - 0.42149332 * medium + 1.586924 * short, + alpha, + ] +} + +// https://drafts.csswg.org/css-color-4/#color-conversion-code +fn xyz50_to_lab(color: Components) -> Components { + const KAPPA: f32 = 24389.0 / 27.0; + const EPSILON: f32 = 216.0 / 24389.0; + let [x, y, z, alpha] = color; + let x_n = 0.3457 / 0.3585; + let z_n = (1.0 - 0.3457 - 0.3585) / 0.3585; + let convert = |value: f32| { + if value > EPSILON { + value.cbrt() + } else { + (KAPPA * value + 16.0) / 116.0 + } + }; + let fx = convert(x / x_n); + let fy = convert(y); + let fz = convert(z / z_n); + [116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz), alpha] +} + +fn xyz65_to_linear_display_p3(color: Components) -> Components { + let [x, y, z, alpha] = color; + [ + 2.493497 * x - 0.9313836 * y - 0.4027108 * z, + -0.829489 * x + 1.7626641 * y + 0.023624686 * z, + 0.03584583 * x - 0.07617239 * y + 0.9568845 * z, + alpha, + ] +} + +fn xyz65_to_linear_a98_rgb(color: Components) -> Components { + let [x, y, z, alpha] = color; + [ + (1829569.0 / 896150.0) * x - (506331.0 / 896150.0) * y - (308931.0 / 896150.0) * z, + -(851781.0 / 878810.0) * x + (1648619.0 / 878810.0) * y + (36519.0 / 878810.0) * z, + (16779.0 / 1248040.0) * x - (147721.0 / 1248040.0) * y + (1266979.0 / 1248040.0) * z, + alpha, + ] +} + +fn xyz50_to_linear_prophoto_rgb(color: Components) -> Components { + let [x, y, z, alpha] = color; + [ + 1.345799 * x - 0.25558022 * y - 0.051118854 * z, + -0.5446225 * x + 1.5082327 * y + 0.020527447 * z, + 1.2119676 * z, + alpha, + ] +} + +fn xyz65_to_linear_rec2020(color: Components) -> Components { + let [x, y, z, alpha] = color; + [ + 1.7166512 * x - 0.35567078 * y - 0.2533663 * z, + -0.6666843 * x + 1.6164812 * y + 0.015768545 * z, + 0.017639857 * x - 0.042770613 * y + 0.94210315 * z, + alpha, + ] +} + +fn linear_a98_rgb_to_a98_rgb(color: Components) -> Components { + let convert = |component: f32| component.signum() * component.abs().powf(256.0 / 563.0); + [convert(color[0]), convert(color[1]), convert(color[2]), color[3]] +} + +fn linear_prophoto_rgb_to_prophoto_rgb(color: Components) -> Components { + let convert = |component: f32| { + if component.abs() <= 1.0 / 512.0 { + component * 16.0 + } else { + component.signum() * component.abs().powf(1.0 / 1.8) + } + }; + [convert(color[0]), convert(color[1]), convert(color[2]), color[3]] +} + +fn linear_rec2020_to_rec2020(color: Components) -> Components { + let convert = |component: f32| { + const ALPHA: f32 = 1.0992968; + const BETA: f32 = 0.01805397; + if component.abs() < BETA { + 4.5 * component + } else { + component.signum() * (ALPHA * component.abs().powf(0.45) - (ALPHA - 1.0)) + } + }; + [convert(color[0]), convert(color[1]), convert(color[2]), color[3]] +} + +pub(crate) fn legacy_color_to_srgb(color_type: u8, color: Components) -> Option { + match color_type { + RGB => Some(color), + HSL => Some(hsl_to_srgb(color)), + HWB => Some(hwb_to_srgb(color)), + _ => None, + } +} + +// https://drafts.csswg.org/css-color-4/#color-conversion-code +pub(crate) fn xyz65_to_oklab(color: Components) -> Components { + let [x, y, z, alpha] = color; + let long = (0.8190224 * x + 0.36190626 * y - 0.12887378 * z).cbrt(); + let medium = (0.03298365 * x + 0.92928684 * y + 0.03614467 * z).cbrt(); + let short = (0.04817719 * x + 0.26423952 * y + 0.63354784 * z).cbrt(); + [ + 0.21045427 * long + 0.7936178 * medium - 0.00407204 * short, + 1.9779985 * long - 2.4285922 * medium + 0.4505937 * short, + 0.02590404 * long + 0.7827717 * medium - 0.80867577 * short, + alpha, + ] +} + +fn to_xyz65(color_type: u8, mut color: Components) -> Option { + match color_type { + RGB => { + color[0] = color[0].clamp(0.0, 1.0); + color[1] = color[1].clamp(0.0, 1.0); + color[2] = color[2].clamp(0.0, 1.0); + Some(linear_srgb_to_xyz65(srgb_to_linear_srgb(color))) + } + SRGB => Some(linear_srgb_to_xyz65(srgb_to_linear_srgb(color))), + SRGB_LINEAR => Some(linear_srgb_to_xyz65(color)), + DISPLAY_P3 => Some(display_p3_to_xyz65(color, false)), + DISPLAY_P3_LINEAR => Some(display_p3_to_xyz65(color, true)), + A98_RGB => Some(a98_rgb_to_xyz65(color)), + PROPHOTO_RGB => Some(xyz50_to_xyz65(prophoto_rgb_to_xyz50(color))), + REC2020 => Some(rec2020_to_xyz65(color)), + XYZ_D50 => Some(xyz50_to_xyz65(color)), + XYZ_D65 => Some(color), + LAB => Some(xyz50_to_xyz65(lab_to_xyz50(color))), + LCH => Some(xyz50_to_xyz65(lab_to_xyz50(polar_to_rectangular(color)))), + OKLAB => Some(oklab_to_xyz65(color)), + OKLCH => Some(oklab_to_xyz65(polar_to_rectangular(color))), + HSL => Some(linear_srgb_to_xyz65(srgb_to_linear_srgb(hsl_to_srgb(color)))), + HWB => Some(linear_srgb_to_xyz65(srgb_to_linear_srgb(hwb_to_srgb(color)))), + _ => None, + } +} + +fn from_xyz65(color_type: u8, color: Components) -> Option { + let srgb = || linear_srgb_to_srgb(xyz65_to_linear_srgb(color)); + match color_type { + RGB | SRGB => Some(srgb()), + SRGB_LINEAR => Some(xyz65_to_linear_srgb(color)), + DISPLAY_P3 => Some(linear_srgb_to_srgb(xyz65_to_linear_display_p3(color))), + DISPLAY_P3_LINEAR => Some(xyz65_to_linear_display_p3(color)), + A98_RGB => Some(linear_a98_rgb_to_a98_rgb(xyz65_to_linear_a98_rgb(color))), + PROPHOTO_RGB => Some(linear_prophoto_rgb_to_prophoto_rgb(xyz50_to_linear_prophoto_rgb( + xyz65_to_xyz50(color), + ))), + REC2020 => Some(linear_rec2020_to_rec2020(xyz65_to_linear_rec2020(color))), + XYZ_D50 => Some(xyz65_to_xyz50(color)), + XYZ_D65 => Some(color), + LAB => Some(xyz50_to_lab(xyz65_to_xyz50(color))), + LCH => Some(rectangular_to_polar(xyz50_to_lab(xyz65_to_xyz50(color)))), + OKLAB => Some(xyz65_to_oklab(color)), + OKLCH => Some(rectangular_to_polar(xyz65_to_oklab(color))), + HSL => Some(srgb_to_hsl(srgb())), + HWB => Some(srgb_to_hwb(srgb())), + _ => None, + } +} + +pub(crate) fn convert(color_type: u8, target_type: u8, color: Components) -> Option { + if color_type == target_type || color_type == RGB && target_type == SRGB { + return Some(color); + } + if target_type == HSL || target_type == HWB { + let srgb = if color_type == RGB || color_type == SRGB { + color + } else { + from_xyz65(SRGB, to_xyz65(color_type, color)?)? + }; + return Some(if target_type == HSL { + srgb_to_hsl(srgb) + } else { + srgb_to_hwb(srgb) + }); + } + if (color_type == HSL || color_type == HWB) && target_type == SRGB { + return legacy_color_to_srgb(color_type, color); + } + from_xyz65(target_type, to_xyz65(color_type, color)?) +} + +pub(crate) fn to_oklab(color_type: u8, color: Components) -> Option { + convert(color_type, OKLAB, color) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn converts_srgb_endpoints_to_oklab() { + assert_eq!(to_oklab(SRGB, [0.0, 0.0, 0.0, 1.0]), Some([0.0, 0.0, 0.0, 1.0])); + let white = to_oklab(SRGB, [1.0, 1.0, 1.0, 1.0]).unwrap(); + assert!((white[0] - 1.0).abs() < 0.00001); + assert!(white[1].abs() < 0.00001); + assert!(white[2].abs() < 0.00001); + } + + #[test] + fn round_trips_supported_color_spaces() { + let source = [0.25, 0.5, 0.75, 0.8]; + for color_type in [ + SRGB, + SRGB_LINEAR, + DISPLAY_P3, + DISPLAY_P3_LINEAR, + A98_RGB, + PROPHOTO_RGB, + REC2020, + XYZ_D50, + XYZ_D65, + LAB, + LCH, + OKLAB, + OKLCH, + HSL, + HWB, + ] { + let converted = convert(SRGB, color_type, source).unwrap(); + let result = convert(color_type, SRGB, converted).unwrap(); + for index in 0..4 { + assert!( + (result[index] - source[index]).abs() < 0.0001, + "color type {color_type}, component {index}" + ); + } + } + } +} diff --git a/Libraries/LibWeb/CSS/Rust/src/color_interpolation.rs b/Libraries/LibWeb/CSS/Rust/src/color_interpolation.rs new file mode 100644 index 0000000000000..dbb4c355e70d8 --- /dev/null +++ b/Libraries/LibWeb/CSS/Rust/src/color_interpolation.rs @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +//! CSS color interpolation over resolved color snapshots. + +// Color values use the same thread-confined shared graph as the rest of the style core. +#![allow(clippy::arc_with_non_send_sync)] + +use std::sync::Arc; + +use crate::color_conversion; +use crate::style_value::{ColorBase, RetainedStyleValueData, StyleValueData}; + +const COLOR_SYNTAX_MODERN: u8 = 1; + +#[repr(C)] +pub struct FfiResolvedColor { + pub color_type: u8, + pub components: [f32; 4], + pub missing: [bool; 4], + pub has_native_components: bool, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum ComponentCategory { + Red, + Green, + Blue, + Lightness, + Colorfulness, + Hue, + OpponentA, + OpponentB, + NotAnalogous, +} + +fn categories_for_color_type(color_type: u8) -> [ComponentCategory; 3] { + use ComponentCategory::*; + use color_conversion::*; + match color_type { + HSL => [Hue, Colorfulness, Lightness], + HWB => [Hue, NotAnalogous, NotAnalogous], + LAB | OKLAB => [Lightness, OpponentA, OpponentB], + LCH | OKLCH => [Lightness, Colorfulness, Hue], + RGB | A98_RGB | DISPLAY_P3 | DISPLAY_P3_LINEAR | SRGB | SRGB_LINEAR | PROPHOTO_RGB | REC2020 | XYZ_D50 + | XYZ_D65 => [Red, Green, Blue], + _ => [NotAnalogous, NotAnalogous, NotAnalogous], + } +} + +// https://drafts.csswg.org/css-color-4/#interpolation-missing +// Carry forward missing components from the input color space to the interpolation color space. +// A missing component is carried forward if it has an analogous component in the target space. +// Additionally, if ALL components of an analogous set are missing, they are all carried forward. +fn carry_forward_missing_components( + source_missing: [bool; 4], + source_categories: [ComponentCategory; 3], + target_categories: [ComponentCategory; 3], +) -> [bool; 4] { + let mut result = [false; 4]; + + // Same-space: all components map to themselves, including NotAnalogous ones (e.g. HWB W/B). + if source_categories == target_categories { + return source_missing; + } + + // Carry forward individual analogous components + for target_index in 0..3 { + if target_categories[target_index] == ComponentCategory::NotAnalogous { + continue; + } + for source_index in 0..3 { + if source_missing[source_index] && source_categories[source_index] == target_categories[target_index] { + result[target_index] = true; + break; + } + } + } + + // If every component of an analogous set is missing in the source, carry forward as a set. + // The analogous set consists of the components that remain after removing individually analogous ones. + let mut all_non_analogous_missing = true; + let mut has_non_analogous = false; + for source_index in 0..3 { + let is_individually_analogous = source_categories[source_index] != ComponentCategory::NotAnalogous + && target_categories.contains(&source_categories[source_index]); + if !is_individually_analogous { + has_non_analogous = true; + if !source_missing[source_index] { + all_non_analogous_missing = false; + } + } + } + if has_non_analogous && all_non_analogous_missing { + for target_index in 0..3 { + let is_individually_analogous = target_categories[target_index] != ComponentCategory::NotAnalogous + && source_categories.contains(&target_categories[target_index]); + if !is_individually_analogous { + result[target_index] = true; + } + } + } + + // Alpha is always analogous to itself. + result[3] = source_missing[3]; + result +} + +fn interpolation_color_type(is_polar: bool, color_space: u8) -> Option { + use color_conversion::*; + if is_polar { + return match color_space { + 0 => Some(HSL), + 1 => Some(HWB), + 2 => Some(LCH), + 3 => Some(OKLCH), + _ => None, + }; + } + match color_space { + 0 => Some(SRGB), + 1 => Some(SRGB_LINEAR), + 2 => Some(DISPLAY_P3), + 3 => Some(DISPLAY_P3_LINEAR), + 4 => Some(A98_RGB), + 5 => Some(PROPHOTO_RGB), + 6 => Some(REC2020), + 7 => Some(LAB), + 8 => Some(OKLAB), + 9 | 11 => Some(XYZ_D65), + 10 => Some(XYZ_D50), + _ => None, + } +} + +fn hue_index(color_type: u8) -> Option { + match color_type { + color_conversion::HSL | color_conversion::HWB => Some(0), + color_conversion::LCH | color_conversion::OKLCH => Some(2), + _ => None, + } +} + +// https://drafts.csswg.org/css-color-4/#hue-interpolation +fn fixup_hues(from: &mut f32, to: &mut f32, hue_interpolation_method: u8) { + let difference = *to - *from; + match hue_interpolation_method { + // https://drafts.csswg.org/css-color-4/#hue-shorter + 0 => { + if difference > 180.0 { + *from += 360.0; + } else if difference < -180.0 { + *to += 360.0; + } + } + // https://drafts.csswg.org/css-color-4/#hue-longer + 1 => { + if difference > 0.0 && difference < 180.0 { + *from += 360.0; + } else if difference > -180.0 && difference <= 0.0 { + *to += 360.0; + } + } + // https://drafts.csswg.org/css-color-4/#hue-increasing + 2 if *to < *from => *to += 360.0, + // https://drafts.csswg.org/css-color-4/#hue-decreasing + 3 if *from < *to => *from += 360.0, + _ => {} + } +} + +fn substitute_missing_components( + from_components: &mut [f32; 4], + to_components: &mut [f32; 4], + from_missing: [bool; 4], + to_missing: [bool; 4], +) { + for index in 0..3 { + if from_missing[index] && !to_missing[index] { + from_components[index] = to_components[index]; + } else if to_missing[index] && !from_missing[index] { + to_components[index] = from_components[index]; + } + } + if from_missing[3] && !to_missing[3] { + from_components[3] = to_components[3]; + } else if to_missing[3] && !from_missing[3] { + to_components[3] = from_components[3]; + } else if from_missing[3] && to_missing[3] { + from_components[3] = 1.0; + to_components[3] = 1.0; + } +} + +fn mark_powerless_hue_after_conversion( + source_type: u8, + target_type: u8, + components: [f32; 4], + missing: &mut [bool; 4], +) { + if source_type == target_type { + return; + } + // NB: Achromatic colors converted through the sRGB -> XYZ-D65 -> XYZ-D50 -> Lab -> LCH chain accumulate + // floating-point error of ~0.016 in the chroma component due to the Bradford chromatic adaptation matrices. + // This is the worst case for all color conversion types, so the threshold is large enough to account for this. + const ACHROMATIC_THRESHOLD: f32 = 0.02; + let powerless = match target_type { + color_conversion::HSL => components[1].abs() < ACHROMATIC_THRESHOLD, + color_conversion::HWB => components[1] + components[2] >= 1.0 - ACHROMATIC_THRESHOLD, + color_conversion::LCH | color_conversion::OKLCH => components[1].abs() < ACHROMATIC_THRESHOLD, + _ => false, + }; + if powerless { + missing[hue_index(target_type).unwrap()] = true; + } +} + +fn retained_number(value: f32) -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Number { value: value as f64 })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn retained_none() -> RetainedStyleValueData { + let value = Arc::into_raw(Arc::new(StyleValueData::Keyword { + keyword: crate::style_compute::none_keyword(), + })); + unsafe { RetainedStyleValueData::from_retained_pointer(value) } +} + +fn retained_component(value: f32, missing: bool) -> RetainedStyleValueData { + if missing { + retained_none() + } else { + retained_number(value) + } +} + +fn make_result(color_type: u8, components: [f32; 4], missing: [bool; 4]) -> StyleValueData { + let (color_type, components, missing) = match color_type { + color_conversion::HSL | color_conversion::HWB => ( + color_conversion::SRGB, + color_conversion::legacy_color_to_srgb(color_type, components).unwrap(), + [false, false, false, missing[3]], + ), + _ => (color_type, components, missing), + }; + StyleValueData::ColorFunction { + color_base: ColorBase { + has_color_type: true, + color_type, + color_syntax: COLOR_SYNTAX_MODERN, + }, + channel_0: retained_component(components[0], missing[0]), + channel_1: retained_component(components[1], missing[1]), + channel_2: retained_component(components[2], missing[2]), + alpha: retained_component(components[3], missing[3]), + has_name: false, + name: unsafe { crate::style_value::RetainedUtf16FlyString::from_leaked_raw(0) }, + origin_color: unsafe { RetainedStyleValueData::from_retained_optional_pointer(std::ptr::null()) }, + } +} + +// https://drafts.csswg.org/css-color-4/#interpolation +fn interpolate( + from: &FfiResolvedColor, + to: &FfiResolvedColor, + is_polar: bool, + color_space: u8, + hue_interpolation_method: u8, + delta: f32, + alpha_multiplier: f32, +) -> Option { + // 1. checking the two colors for analogous components and analogous sets which will be carried forward + let target_type = interpolation_color_type(is_polar, color_space)?; + + // 2. prepare both colors for conversion. this changes any powerless components to missing values + let mut from_source_missing = from.missing; + let mut to_source_missing = to.missing; + if !from.has_native_components && !from.missing[3] && from.components[3] == 0.0 { + from_source_missing[..3].fill(true); + } + if !to.has_native_components && !to.missing[3] && to.components[3] == 0.0 { + to_source_missing[..3].fill(true); + } + + // 3. converting them both to a given color space which will be referred to as the interpolation color space + // below. + let mut from_components = color_conversion::convert(from.color_type, target_type, from.components)?; + let mut to_components = color_conversion::convert(to.color_type, target_type, to.components)?; + let target_categories = categories_for_color_type(target_type); + let mut from_missing = carry_forward_missing_components( + from_source_missing, + categories_for_color_type(from.color_type), + target_categories, + ); + let mut to_missing = carry_forward_missing_components( + to_source_missing, + categories_for_color_type(to.color_type), + target_categories, + ); + if is_polar { + mark_powerless_hue_after_conversion(from.color_type, target_type, from_components, &mut from_missing); + mark_powerless_hue_after_conversion(to.color_type, target_type, to_components, &mut to_missing); + } + + // 4. (if required) re-inserting carried forward values in the converted colors + let both_alpha_missing = from_missing[3] && to_missing[3]; + substitute_missing_components(&mut from_components, &mut to_components, from_missing, to_missing); + + // 5. (if required) fixing up the hues, depending on the selected + if let Some(index) = hue_index(target_type) { + fixup_hues( + &mut from_components[index], + &mut to_components[index], + hue_interpolation_method, + ); + } + + let interpolate = |from: f32, to: f32| from + (to - from) * delta; + let interpolated_alpha = interpolate(from_components[3], to_components[3]).clamp(0.0, 1.0); + let mut result = if interpolated_alpha == 0.0 && !both_alpha_missing { + // OPTIMIZATION: Fully transparent results can skip the premultiply/interpolate/unpremultiply cycle. + [0.0; 4] + } else { + // 6. changing the color components to premultiplied form + // https://drafts.csswg.org/css-color-4/#interpolation-alpha + // For rectangular orthogonal color coordinate systems, all component values are multiplied by the alpha value. + // For cylindrical polar color coordinate systems, the hue angle is NOT premultiplied. + let hue_index = hue_index(target_type); + let premultiply = |components: [f32; 4], index: usize| { + if Some(index) == hue_index { + components[index] + } else { + components[index] * components[3] + } + }; + + // 7. linearly interpolating each component of the computed value of the color separately + let premultiplied = [ + interpolate(premultiply(from_components, 0), premultiply(to_components, 0)), + interpolate(premultiply(from_components, 1), premultiply(to_components, 1)), + interpolate(premultiply(from_components, 2), premultiply(to_components, 2)), + ]; + + // 8. undoing premultiplication + let unpremultiply = |value: f32, index: usize| { + if Some(index) == hue_index { + value + } else { + value / interpolated_alpha + } + }; + [ + unpremultiply(premultiplied[0], 0), + unpremultiply(premultiplied[1], 1), + unpremultiply(premultiplied[2], 2), + interpolated_alpha, + ] + }; + result[3] *= alpha_multiplier; + let missing = [ + from_missing[0] && to_missing[0], + from_missing[1] && to_missing[1], + from_missing[2] && to_missing[2], + from_missing[3] && to_missing[3], + ]; + Some(make_result(target_type, result, missing)) +} + +/// # Safety +/// +/// All pointers must be non-null and point at live values for the duration of this call. The returned pointer owns +/// one strong reference and must be adopted or released by the caller. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_interpolate_color( + from: *const FfiResolvedColor, + to: *const FfiResolvedColor, + color_interpolation_method: *const StyleValueData, + delta: f32, + alpha_multiplier: f32, +) -> *const StyleValueData { + crate::abort_on_panic(|| { + let (Some(from), Some(to), Some(method)) = (unsafe { from.as_ref() }, unsafe { to.as_ref() }, unsafe { + color_interpolation_method.as_ref() + }) else { + return std::ptr::null(); + }; + let StyleValueData::ColorInterpolationMethod { + is_polar, + color_space, + hue_interpolation_method, + } = method + else { + return std::ptr::null(); + }; + interpolate( + from, + to, + *is_polar, + *color_space, + *hue_interpolation_method, + delta, + alpha_multiplier, + ) + .map(|result| Arc::into_raw(Arc::new(result))) + .unwrap_or(std::ptr::null()) + }) +} diff --git a/Libraries/LibWeb/CSS/Rust/src/computed_values.rs b/Libraries/LibWeb/CSS/Rust/src/computed_values.rs index a5337f1c4e7fc..69d92d39de387 100644 --- a/Libraries/LibWeb/CSS/Rust/src/computed_values.rs +++ b/Libraries/LibWeb/CSS/Rust/src/computed_values.rs @@ -6,13 +6,12 @@ //! Rust ownership of the ComputedValues style group payloads. //! -//! Each style value group payload is a C++ struct that Rust treats as an opaque -//! blob: C++ registers a vtable per group with the payload size, alignment and -//! callbacks for default-construction, copy-construction and destruction, in -//! the same way Stylo drives Gecko's nsStyle* structs. Rust owns allocation, -//! layout and destruction; reference counting happens through an atomic header -//! placed immediately before the payload, which the C++ side reads and updates -//! inline so that sharing a payload never crosses the FFI boundary. +//! Rust-native style groups define their payload layout and lifecycle here. +//! Groups that still contain C++-owned field types register their payload size, +//! alignment and lifecycle callbacks, in the same way Stylo drives Gecko's +//! nsStyle* structs. Rust owns allocation and reference counting for both kinds; +//! the atomic header is placed immediately before the payload, which the C++ +//! side reads and updates inline so that sharing never crosses the FFI boundary. //! //! Layout contract with the C++ side (StyleStructRef): //! @@ -38,12 +37,11 @@ pub const STYLE_GROUP_STATIC_REFCOUNT: usize = usize::MAX; /// Layout of the inherited box style value group. /// /// This is the source of truth for the group's payload layout: C++ derives its -/// group struct from the cbindgen mirror of this type, adding the initial -/// values and typed accessors on top. The fields hold C++ `enum class : u8` -/// values that Rust stores as opaque bytes, keeping the enum definitions -/// single-sourced in C++. +/// group struct from the cbindgen mirror of this type, adding its C++ identity +/// and typed accessors on top. The fields hold C++ `enum class : u8` values +/// generated from CSS/Enums.json, the same source as the C++ enums. #[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct InheritedBoxValues { pub visibility: u8, pub direction: u8, @@ -52,18 +50,225 @@ pub struct InheritedBoxValues { pub image_rendering: u8, } -/// Size, alignment and lifecycle callbacks for one style value group type. +/// The computed forms accepted by width and height sizing properties. +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ComputedSizeKind { + Auto, + Calculated, + Length, + Percentage, + MinContent, + MaxContent, + FitContent, + None, +} + +/// A computed sizing value. Scalar and calculated values retain their +/// immutable Rust style-value identity. Fit-content retains only its argument, +/// and keyword-only forms leave the handle empty. +#[repr(C)] +pub struct ComputedStyleValueHandle { + pub pointer: *const c_void, +} + +impl ComputedStyleValueHandle { + fn empty() -> Self { + Self { + pointer: std::ptr::null(), + } + } + + fn retained(data: *const crate::style_value::StyleValueData) -> Self { + Self { + pointer: unsafe { crate::style_value::rust_style_value_retain(data) }.cast(), + } + } + + fn length(value: f64) -> Self { + Self { + pointer: crate::style_value::rust_style_value_create_length(value, crate::style_compute::px_length_unit()) + .cast(), + } + } + + fn data(&self) -> Option<&crate::style_value::StyleValueData> { + unsafe { self.pointer.cast::().as_ref() } + } +} + +impl Clone for ComputedStyleValueHandle { + fn clone(&self) -> Self { + Self { + pointer: unsafe { crate::style_value::rust_style_value_retain(self.pointer.cast()) }.cast(), + } + } +} + +impl Drop for ComputedStyleValueHandle { + fn drop(&mut self) { + unsafe { crate::style_value::rust_style_value_release(self.pointer.cast()) }; + } +} + +impl PartialEq for ComputedStyleValueHandle { + fn eq(&self, other: &Self) -> bool { + match (self.data(), other.data()) { + (Some(first), Some(second)) => std::ptr::eq(first, second) || first == second, + (None, None) => true, + _ => false, + } + } +} + +#[repr(C)] +pub struct ComputedSize { + pub kind: ComputedSizeKind, + pub value: ComputedStyleValueHandle, +} + +impl Clone for ComputedSize { + fn clone(&self) -> Self { + Self { + kind: self.kind, + value: self.value.clone(), + } + } +} + +impl PartialEq for ComputedSize { + fn eq(&self, other: &Self) -> bool { + self.kind == other.kind && self.value == other.value + } +} + +/// Layout of the six computed sizing properties. +#[repr(C)] +#[derive(Clone, PartialEq)] +pub struct SizingValues { + pub width: ComputedSize, + pub min_width: ComputedSize, + pub max_width: ComputedSize, + pub height: ComputedSize, + pub min_height: ComputedSize, + pub max_height: ComputedSize, +} + +/// A computed flex-basis value. Content uses the flag; every other form uses +/// the same computed-size representation as width and height. +#[repr(C)] +#[derive(Clone, PartialEq)] +pub struct ComputedFlexBasis { + pub is_content: bool, + pub size: ComputedSize, +} + +/// A computed row-gap or column-gap value. Normal uses the flag; every other +/// form retains its immutable length-percentage identity. +#[repr(C)] +#[derive(Clone, PartialEq)] +pub struct ComputedGap { + pub is_normal: bool, + pub value: ComputedStyleValueHandle, +} + +/// Layout of the computed flexbox and box-alignment properties. +#[repr(C)] +#[derive(Clone, PartialEq)] +pub struct AlignmentValues { + pub flex_direction: u8, + pub flex_wrap: u8, + pub flex_basis: ComputedFlexBasis, + pub flex_grow: f64, + pub flex_shrink: f64, + pub order: i32, + pub align_content: u8, + pub align_items: u8, + pub align_self: u8, + pub justify_content: u8, + pub justify_items: u8, + pub justify_self: u8, + pub column_gap: ComputedGap, + pub row_gap: ComputedGap, +} + +/// A computed length-percentage-or-auto value. +#[repr(C)] +#[derive(Clone, PartialEq)] +pub struct ComputedLengthPercentageOrAuto { + pub is_auto: bool, + pub value: ComputedStyleValueHandle, +} + +/// Four physical computed length-percentage-or-auto sides. +#[repr(C)] +#[derive(Clone, PartialEq)] +pub struct ComputedLengthBox { + pub top: ComputedLengthPercentageOrAuto, + pub right: ComputedLengthPercentageOrAuto, + pub bottom: ComputedLengthPercentageOrAuto, + pub left: ComputedLengthPercentageOrAuto, +} + +/// Layout of the computed inset, margin, and padding properties. +#[repr(C)] +#[derive(Clone, PartialEq)] +pub struct SurroundValues { + pub inset: ComputedLengthBox, + pub top_anchor_inset: ComputedStyleValueHandle, + pub right_anchor_inset: ComputedStyleValueHandle, + pub bottom_anchor_inset: ComputedStyleValueHandle, + pub left_anchor_inset: ComputedStyleValueHandle, + pub margin: ComputedLengthBox, + pub padding: ComputedLengthBox, +} + +/// Layout of the non-inherited SVG geometry and painting properties. +#[repr(C)] +#[derive(Clone, PartialEq)] +pub struct SVGResetValues { + pub cx: ComputedStyleValueHandle, + pub cy: ComputedStyleValueHandle, + pub r: ComputedStyleValueHandle, + pub rx: ComputedLengthPercentageOrAuto, + pub ry: ComputedLengthPercentageOrAuto, + pub x: ComputedStyleValueHandle, + pub y: ComputedStyleValueHandle, + pub stop_color: u32, + pub stop_opacity: f32, + pub flood_color: u32, + pub flood_opacity: f32, + pub vector_effect: u8, + pub shape_rendering: u8, +} + +/// Selects the language that owns a style group's payload lifecycle. +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum StyleGroupLifecycle { + Cpp, + InheritedTable, + InheritedBox, + Sizing, + Alignment, + SVGReset, + Surround, +} + +/// Size, alignment and optional C++ lifecycle callbacks for one style value +/// group type. Rust-native groups leave the callbacks null. #[repr(C)] #[derive(Clone, Copy)] pub struct StyleGroupVTable { + pub lifecycle: StyleGroupLifecycle, pub size: usize, pub align: usize, - pub default_construct: unsafe extern "C" fn(payload: *mut c_void), - pub copy_construct: unsafe extern "C" fn(payload: *mut c_void, source: *const c_void), - pub destruct: unsafe extern "C" fn(payload: *mut c_void), + pub default_construct: Option, + pub copy_construct: Option, + pub destruct: Option, /// Field-wise payload equality; groups without a comparable layout /// report false, which conservatively disables payload sharing. - pub equals: unsafe extern "C" fn(a: *const c_void, b: *const c_void) -> bool, + pub equals: Option bool>, } // SAFETY: The function pointers are stateless C++ callbacks and the plain @@ -89,6 +294,110 @@ fn vtable(group_index: usize) -> &'static StyleGroupVTable { ®istry.vtables[group_index] } +fn payload_size(table: &StyleGroupVTable) -> usize { + match table.lifecycle { + StyleGroupLifecycle::Cpp => table.size, + StyleGroupLifecycle::InheritedTable => size_of::(), + StyleGroupLifecycle::InheritedBox => size_of::(), + StyleGroupLifecycle::Sizing => size_of::(), + StyleGroupLifecycle::Alignment => size_of::(), + StyleGroupLifecycle::SVGReset => size_of::(), + StyleGroupLifecycle::Surround => size_of::(), + } +} + +fn payload_align(table: &StyleGroupVTable) -> usize { + match table.lifecycle { + StyleGroupLifecycle::Cpp => table.align, + StyleGroupLifecycle::InheritedTable => align_of::(), + StyleGroupLifecycle::InheritedBox => align_of::(), + StyleGroupLifecycle::Sizing => align_of::(), + StyleGroupLifecycle::Alignment => align_of::(), + StyleGroupLifecycle::SVGReset => align_of::(), + StyleGroupLifecycle::Surround => align_of::(), + } +} + +unsafe fn default_construct(table: &StyleGroupVTable, payload: *mut c_void) { + match table.lifecycle { + StyleGroupLifecycle::Cpp => unsafe { + table.default_construct.expect("missing C++ style group constructor")(payload); + }, + StyleGroupLifecycle::InheritedTable => unsafe { + (payload as *mut InheritedTableValues).write(InheritedTableValues::initial()); + }, + StyleGroupLifecycle::InheritedBox => unsafe { + (payload as *mut InheritedBoxValues).write(InheritedBoxValues::initial()); + }, + StyleGroupLifecycle::Sizing => unsafe { + (payload as *mut SizingValues).write(SizingValues::initial()); + }, + StyleGroupLifecycle::Alignment => unsafe { + (payload as *mut AlignmentValues).write(AlignmentValues::initial()); + }, + StyleGroupLifecycle::SVGReset => unsafe { + (payload as *mut SVGResetValues).write(SVGResetValues::initial()); + }, + StyleGroupLifecycle::Surround => unsafe { + (payload as *mut SurroundValues).write(SurroundValues::initial()); + }, + } +} + +unsafe fn copy_construct(table: &StyleGroupVTable, payload: *mut c_void, source: *const c_void) { + match table.lifecycle { + StyleGroupLifecycle::Cpp => unsafe { + table.copy_construct.expect("missing C++ style group copy constructor")(payload, source); + }, + StyleGroupLifecycle::InheritedTable => unsafe { + (payload as *mut InheritedTableValues).write(*(source as *const InheritedTableValues)); + }, + StyleGroupLifecycle::InheritedBox => unsafe { + (payload as *mut InheritedBoxValues).write(*(source as *const InheritedBoxValues)); + }, + StyleGroupLifecycle::Sizing => unsafe { + (payload as *mut SizingValues).write((*(source as *const SizingValues)).clone()); + }, + StyleGroupLifecycle::Alignment => unsafe { + (payload as *mut AlignmentValues).write((*(source as *const AlignmentValues)).clone()); + }, + StyleGroupLifecycle::SVGReset => unsafe { + (payload as *mut SVGResetValues).write((*(source as *const SVGResetValues)).clone()); + }, + StyleGroupLifecycle::Surround => unsafe { + (payload as *mut SurroundValues).write((*(source as *const SurroundValues)).clone()); + }, + } +} + +unsafe fn destruct(table: &StyleGroupVTable, payload: *mut c_void) { + match table.lifecycle { + StyleGroupLifecycle::Cpp => unsafe { table.destruct.expect("missing C++ style group destructor")(payload) }, + StyleGroupLifecycle::InheritedTable => unsafe { std::ptr::drop_in_place(payload as *mut InheritedTableValues) }, + StyleGroupLifecycle::InheritedBox => unsafe { std::ptr::drop_in_place(payload as *mut InheritedBoxValues) }, + StyleGroupLifecycle::Sizing => unsafe { std::ptr::drop_in_place(payload as *mut SizingValues) }, + StyleGroupLifecycle::Alignment => unsafe { std::ptr::drop_in_place(payload as *mut AlignmentValues) }, + StyleGroupLifecycle::SVGReset => unsafe { std::ptr::drop_in_place(payload as *mut SVGResetValues) }, + StyleGroupLifecycle::Surround => unsafe { std::ptr::drop_in_place(payload as *mut SurroundValues) }, + } +} + +unsafe fn payloads_equal(table: &StyleGroupVTable, a: *const c_void, b: *const c_void) -> bool { + match table.lifecycle { + StyleGroupLifecycle::Cpp => unsafe { table.equals.is_some_and(|equals| equals(a, b)) }, + StyleGroupLifecycle::InheritedTable => unsafe { + *(a as *const InheritedTableValues) == *(b as *const InheritedTableValues) + }, + StyleGroupLifecycle::InheritedBox => unsafe { + *(a as *const InheritedBoxValues) == *(b as *const InheritedBoxValues) + }, + StyleGroupLifecycle::Sizing => unsafe { *(a as *const SizingValues) == *(b as *const SizingValues) }, + StyleGroupLifecycle::Alignment => unsafe { *(a as *const AlignmentValues) == *(b as *const AlignmentValues) }, + StyleGroupLifecycle::SVGReset => unsafe { *(a as *const SVGResetValues) == *(b as *const SVGResetValues) }, + StyleGroupLifecycle::Surround => unsafe { *(a as *const SurroundValues) == *(b as *const SurroundValues) }, + } +} + pub(crate) fn default_group_payload(group_index: usize) -> *const c_void { REGISTRY.get().expect("style groups used before registration").defaults[group_index] } @@ -96,7 +405,7 @@ pub(crate) fn default_group_payload(group_index: usize) -> *const c_void { /// Retains one reference to a payload, mirroring StyleStructRef::ref(): /// intentionally leaked payloads are never counted. pub(crate) fn retain_group_payload(group_index: usize, payload: *const c_void) { - let refcount = refcount_of(payload, vtable(group_index).align); + let refcount = refcount_of(payload, payload_align(vtable(group_index))); if refcount.load(Ordering::Relaxed) == STYLE_GROUP_STATIC_REFCOUNT { return; } @@ -108,8 +417,10 @@ fn header_size(align: usize) -> usize { } fn allocation_layout(vtable: &StyleGroupVTable) -> Layout { - let align = vtable.align.max(align_of::()); - Layout::from_size_align(header_size(vtable.align) + vtable.size, align).expect("style group layout overflow") + let payload_align = payload_align(vtable); + let align = payload_align.max(align_of::()); + Layout::from_size_align(header_size(payload_align) + payload_size(vtable), align) + .expect("style group layout overflow") } fn refcount_of(payload: *const c_void, align: usize) -> &'static AtomicUsize { @@ -130,7 +441,7 @@ fn allocate_payload(vtable: &StyleGroupVTable, initial_refcount: usize) -> *mut } let header = allocation as *mut AtomicUsize; (*header).store(initial_refcount, Ordering::Relaxed); - allocation.add(header_size(vtable.align)) as *mut c_void + allocation.add(header_size(payload_align(vtable))) as *mut c_void } } @@ -151,9 +462,15 @@ pub unsafe extern "C" fn rust_style_group_registry_register( let tables: Box<[StyleGroupVTable]> = std::slice::from_raw_parts(vtables, count).into(); let mut defaults = Vec::with_capacity(count); for (index, table) in tables.iter().enumerate() { - assert!(table.align.is_power_of_two()); + assert!(payload_align(table).is_power_of_two()); + assert_eq!(table.size, payload_size(table), "style group size disagrees across FFI"); + assert_eq!( + table.align, + payload_align(table), + "style group alignment disagrees across FFI" + ); let payload = allocate_payload(table, STYLE_GROUP_STATIC_REFCOUNT); - (table.default_construct)(payload); + default_construct(table, payload); *out_default_payloads.add(index) = payload; defaults.push(payload as *const c_void); } @@ -180,7 +497,7 @@ pub unsafe extern "C" fn rust_style_group_clone(group_index: usize, source: *con abort_on_panic(|| unsafe { let table = vtable(group_index); let payload = allocate_payload(table, 1); - (table.copy_construct)(payload, source); + copy_construct(table, payload, source); payload }) } @@ -195,9 +512,9 @@ pub unsafe extern "C" fn rust_style_group_free(group_index: usize, payload: *mut crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StyleGroupFreeEntry); abort_on_panic(|| unsafe { let table = vtable(group_index); - debug_assert!(refcount_of(payload, table.align).load(Ordering::Relaxed) == 0); - (table.destruct)(payload); - let allocation = (payload as *mut u8).sub(header_size(table.align)); + debug_assert!(refcount_of(payload, payload_align(table)).load(Ordering::Relaxed) == 0); + destruct(table, payload); + let allocation = (payload as *mut u8).sub(header_size(payload_align(table))); dealloc(allocation, allocation_layout(table)); }); } @@ -256,9 +573,9 @@ pub const GROUP_FIELD_REQUIRE_INITIAL_VALUE: u8 = 11; pub const GROUP_FIELD_CSS_PIXELS_NON_NEGATIVE: u8 = 12; /// A number stored as f64, resolved by the C++ gather loop. pub const GROUP_FIELD_RESOLVED_F64: u8 = 13; -/// The value's shell stored into a single-pointer reference slot, retaining +/// The value's Rust data stored into a single-pointer handle slot, retaining /// one reference; the slot's constructor default must be null. -pub const GROUP_FIELD_RETAINED_SHELL: u8 = 14; +pub const GROUP_FIELD_RETAINED_DATA: u8 = 14; /// A bool stored as one byte: whether the value is the descriptor's keyword. pub const GROUP_FIELD_KEYWORD_EQUALS_BOOL: u8 = 15; /// A byte resolved by the C++ gather loop, carried in the resolved number, @@ -266,10 +583,9 @@ pub const GROUP_FIELD_KEYWORD_EQUALS_BOOL: u8 = 15; pub const GROUP_FIELD_RESOLVED_U8: u8 = 16; /// One gathered value for the generic group builder: the computed value's -/// shell and data, plus the resolved raw color for color-kind fields. +/// data, plus the resolved raw color for color-kind fields. #[repr(C)] pub struct FfiGroupValueEntry { - pub shell: *const c_void, pub data: *const c_void, pub resolved_color: u32, pub has_resolved_color: bool, @@ -315,7 +631,7 @@ pub unsafe extern "C" fn rust_style_group_register_field_descriptors( /// the parent or default payload when the result compares equal. /// /// # Safety -/// `values` must hold one valid (shell, data) entry per registered descriptor +/// `values` must hold one valid data entry per registered descriptor /// of the group, in registration order; `parent_payload` must be a valid /// payload of the group or null. #[unsafe(no_mangle)] @@ -345,7 +661,7 @@ pub unsafe extern "C" fn rust_build_style_group( I32(u32, i32), U64(u32, u64), U32(u32, u32), - Shell(u32, *const c_void), + Data(u32, *const StyleValueData), } let mut pokes = Vec::with_capacity(count); for (descriptor, value) in descriptors.iter().zip(values) { @@ -398,6 +714,13 @@ pub unsafe extern "C" fn rust_build_style_group( pokes.push(Poke::U64(descriptor.offset, *value as u64)); } GROUP_FIELD_REQUIRE_KEYWORD => { + // NB: Repeatable-list properties keep even a single computed item in a value list. + let data = match data { + StyleValueData::ValueList { values, .. } if values.as_slice().len() == 1 => { + values.as_slice()[0].data() + } + data => data, + }; let StyleValueData::Keyword { keyword } = data else { return None; }; @@ -441,7 +764,7 @@ pub unsafe extern "C" fn rust_build_style_group( } }, GROUP_FIELD_REQUIRE_INITIAL_VALUE => { - if value.data != crate::style_compute::initial_value(descriptor.property_id).data { + if value.data != crate::style_compute::initial_value_data(descriptor.property_id).cast() { return None; } } @@ -463,11 +786,8 @@ pub unsafe extern "C" fn rust_build_style_group( } pokes.push(Poke::F64(descriptor.offset, value.resolved_number)); } - GROUP_FIELD_RETAINED_SHELL => { - if value.shell.is_null() { - return None; - } - pokes.push(Poke::Shell(descriptor.offset, value.shell)); + GROUP_FIELD_RETAINED_DATA => { + pokes.push(Poke::Data(descriptor.offset, value.data.cast())); } GROUP_FIELD_KEYWORD_EQUALS_BOOL => { let is_keyword = @@ -489,7 +809,7 @@ pub unsafe extern "C" fn rust_build_style_group( // SAFETY: The scratch payload was allocated for this group's layout, // and every poke offset comes from offsetof on the C++ side. unsafe { - (table.default_construct)(scratch); + default_construct(table, scratch); for poke in &pokes { let base = scratch as *mut u8; match *poke { @@ -499,28 +819,28 @@ pub unsafe extern "C" fn rust_build_style_group( Poke::I32(offset, value) => *(base.add(offset as usize) as *mut i32) = value, Poke::U64(offset, value) => *(base.add(offset as usize) as *mut u64) = value, Poke::U32(offset, value) => *(base.add(offset as usize) as *mut u32) = value, - Poke::Shell(offset, shell) => { + Poke::Data(offset, data) => { // The slot's constructor default is null, so nothing is released. - crate::style_value::retain_shell_pointer(shell); - *(base.add(offset as usize) as *mut *const c_void) = shell; + let retained = crate::style_value::rust_style_value_retain(data); + *(base.add(offset as usize) as *mut *const StyleValueData) = retained; } } } } let free_scratch = || unsafe { - (table.destruct)(scratch); - let allocation = (scratch as *mut u8).sub(header_size(table.align)); + destruct(table, scratch); + let allocation = (scratch as *mut u8).sub(header_size(payload_align(table))); dealloc(allocation, allocation_layout(table)); }; - if !parent_payload.is_null() && unsafe { (table.equals)(scratch, parent_payload) } { + if !parent_payload.is_null() && unsafe { payloads_equal(table, scratch, parent_payload) } { free_scratch(); retain_group_payload(group_index, parent_payload); return Some(parent_payload); } let default_payload = default_group_payload(group_index); - if unsafe { (table.equals)(scratch, default_payload) } { + if unsafe { payloads_equal(table, scratch, default_payload) } { free_scratch(); return Some(default_payload); } @@ -591,6 +911,529 @@ pub unsafe extern "C" fn rust_build_inherited_box_group( .unwrap_or(std::ptr::null()) } +impl ComputedSize { + fn keyword(kind: ComputedSizeKind) -> Self { + Self { + kind, + value: ComputedStyleValueHandle::empty(), + } + } + + fn retained(kind: ComputedSizeKind, data: *const crate::style_value::StyleValueData) -> Self { + Self { + kind, + value: ComputedStyleValueHandle::retained(data), + } + } + + fn from_data(data: *const c_void) -> Self { + use crate::css_enums::keyword; + use crate::style_value::StyleValueData; + + let data = data.cast::(); + match unsafe { data.as_ref() } { + Some(StyleValueData::Keyword { keyword: value }) if *value == keyword::AUTO => { + Self::keyword(ComputedSizeKind::Auto) + } + Some(StyleValueData::Keyword { keyword: value }) if *value == keyword::FIT_CONTENT => { + Self::keyword(ComputedSizeKind::FitContent) + } + Some(StyleValueData::Keyword { keyword: value }) if *value == keyword::MIN_CONTENT => { + Self::keyword(ComputedSizeKind::MinContent) + } + Some(StyleValueData::Keyword { keyword: value }) if *value == keyword::MAX_CONTENT => { + Self::keyword(ComputedSizeKind::MaxContent) + } + Some(StyleValueData::Keyword { keyword: value }) if *value == keyword::NONE => { + Self::keyword(ComputedSizeKind::None) + } + Some(StyleValueData::Function { value, .. }) => { + Self::retained(ComputedSizeKind::FitContent, value.pointer()) + } + Some(StyleValueData::Calculated { .. }) => Self::retained(ComputedSizeKind::Calculated, data), + Some(StyleValueData::Percentage { .. }) => Self::retained(ComputedSizeKind::Percentage, data), + Some(StyleValueData::Length { .. }) => Self::retained(ComputedSizeKind::Length, data), + // FIXME: Support `anchor-size(..)`. + Some(StyleValueData::AnchorSize { .. }) => Self::keyword(ComputedSizeKind::None), + _ => Self::keyword(ComputedSizeKind::Auto), + } + } +} + +impl SizingValues { + fn initial() -> Self { + Self { + width: ComputedSize::keyword(ComputedSizeKind::Auto), + min_width: ComputedSize::keyword(ComputedSizeKind::Auto), + max_width: ComputedSize::keyword(ComputedSizeKind::None), + height: ComputedSize::keyword(ComputedSizeKind::Auto), + min_height: ComputedSize::keyword(ComputedSizeKind::Auto), + max_height: ComputedSize::keyword(ComputedSizeKind::None), + } + } +} + +impl ComputedFlexBasis { + fn from_data(data: *const c_void) -> Self { + use crate::css_enums::keyword; + use crate::style_value::StyleValueData; + + let is_content = matches!( + unsafe { data.cast::().as_ref() }, + Some(StyleValueData::Keyword { keyword }) if *keyword == keyword::CONTENT + ); + Self { + is_content, + size: if is_content { + ComputedSize::keyword(ComputedSizeKind::Auto) + } else { + ComputedSize::from_data(data) + }, + } + } +} + +impl ComputedGap { + fn from_data(data: *const c_void) -> Self { + use crate::css_enums::keyword; + use crate::style_value::StyleValueData; + + let data = data.cast::(); + let is_normal = matches!( + unsafe { data.as_ref() }, + Some(StyleValueData::Keyword { keyword }) if *keyword == keyword::NORMAL + ); + Self { + is_normal, + value: if is_normal { + ComputedStyleValueHandle::empty() + } else { + ComputedStyleValueHandle::retained(data) + }, + } + } +} + +impl AlignmentValues { + fn initial() -> Self { + use crate::css_enums::{ + align_content, align_items, align_self, flex_direction, flex_wrap, justify_content, justify_items, + justify_self, + }; + + Self { + flex_direction: flex_direction::ROW, + flex_wrap: flex_wrap::NOWRAP, + flex_basis: ComputedFlexBasis { + is_content: false, + size: ComputedSize::keyword(ComputedSizeKind::Auto), + }, + flex_grow: 0.0, + flex_shrink: 1.0, + order: 0, + align_content: align_content::STRETCH, + align_items: align_items::STRETCH, + align_self: align_self::AUTO, + justify_content: justify_content::FLEX_START, + justify_items: justify_items::LEGACY, + justify_self: justify_self::AUTO, + column_gap: ComputedGap { + is_normal: true, + value: ComputedStyleValueHandle::empty(), + }, + row_gap: ComputedGap { + is_normal: true, + value: ComputedStyleValueHandle::empty(), + }, + } + } +} + +impl ComputedLengthPercentageOrAuto { + fn auto() -> Self { + Self { + is_auto: true, + value: ComputedStyleValueHandle::empty(), + } + } + + fn zero() -> Self { + Self { + is_auto: false, + value: ComputedStyleValueHandle::length(0.0), + } + } + + fn from_data(data: *const c_void) -> Self { + use crate::css_enums::keyword; + use crate::style_value::StyleValueData; + + let data = data.cast::(); + let is_auto = matches!( + unsafe { data.as_ref() }, + Some(StyleValueData::Keyword { keyword }) if *keyword == keyword::AUTO + ); + Self { + is_auto, + value: if is_auto { + ComputedStyleValueHandle::empty() + } else { + ComputedStyleValueHandle::retained(data) + }, + } + } + + fn from_length_box_data(data: *const c_void, default_is_auto: bool) -> Self { + use crate::css_enums::keyword; + use crate::style_value::StyleValueData; + + let data = data.cast::(); + match unsafe { data.as_ref() } { + Some(StyleValueData::Keyword { keyword: value }) if *value == keyword::AUTO => Self::auto(), + Some( + StyleValueData::Length { .. } | StyleValueData::Percentage { .. } | StyleValueData::Calculated { .. }, + ) => Self { + is_auto: false, + value: ComputedStyleValueHandle::retained(data), + }, + _ if default_is_auto => Self::auto(), + _ => Self::zero(), + } + } +} + +impl ComputedLengthBox { + fn auto() -> Self { + Self { + top: ComputedLengthPercentageOrAuto::auto(), + right: ComputedLengthPercentageOrAuto::auto(), + bottom: ComputedLengthPercentageOrAuto::auto(), + left: ComputedLengthPercentageOrAuto::auto(), + } + } + + fn zero() -> Self { + let zero = ComputedStyleValueHandle::length(0.0); + let side = || ComputedLengthPercentageOrAuto { + is_auto: false, + value: zero.clone(), + }; + Self { + top: side(), + right: side(), + bottom: side(), + left: side(), + } + } + + fn from_data( + top: *const c_void, + right: *const c_void, + bottom: *const c_void, + left: *const c_void, + default_is_auto: bool, + ) -> Self { + Self { + top: ComputedLengthPercentageOrAuto::from_length_box_data(top, default_is_auto), + right: ComputedLengthPercentageOrAuto::from_length_box_data(right, default_is_auto), + bottom: ComputedLengthPercentageOrAuto::from_length_box_data(bottom, default_is_auto), + left: ComputedLengthPercentageOrAuto::from_length_box_data(left, default_is_auto), + } + } +} + +impl SurroundValues { + fn initial() -> Self { + Self { + inset: ComputedLengthBox::auto(), + top_anchor_inset: ComputedStyleValueHandle::empty(), + right_anchor_inset: ComputedStyleValueHandle::empty(), + bottom_anchor_inset: ComputedStyleValueHandle::empty(), + left_anchor_inset: ComputedStyleValueHandle::empty(), + margin: ComputedLengthBox::zero(), + padding: ComputedLengthBox::zero(), + } + } +} + +impl SVGResetValues { + fn initial() -> Self { + use crate::css_enums::{shape_rendering, vector_effect}; + + const OPAQUE_BLACK_BGRA: u32 = 0xff00_0000; + + let zero = ComputedStyleValueHandle::length(0.0); + Self { + cx: zero.clone(), + cy: zero.clone(), + r: zero.clone(), + rx: ComputedLengthPercentageOrAuto { + is_auto: true, + value: ComputedStyleValueHandle::empty(), + }, + ry: ComputedLengthPercentageOrAuto { + is_auto: true, + value: ComputedStyleValueHandle::empty(), + }, + x: zero.clone(), + y: zero, + stop_color: OPAQUE_BLACK_BGRA, + stop_opacity: 1.0, + flood_color: OPAQUE_BLACK_BGRA, + flood_opacity: 1.0, + vector_effect: vector_effect::NONE, + shape_rendering: shape_rendering::AUTO, + } + } +} + +/// Builds the complete alignment group from its computed property values. +/// +/// The returned payload carries one reference for the caller; fresh payloads +/// start at one, shared payloads are retained, and default payloads are +/// intentionally leaked and never counted. +/// +/// # Safety +/// The value pointers must identify valid StyleValueData, and +/// `parent_payload` must identify an alignment payload or be null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_build_alignment_group( + group_index: usize, + flex_direction: *const c_void, + flex_wrap: *const c_void, + flex_basis: *const c_void, + flex_grow: f64, + flex_shrink: f64, + order: i32, + align_content: *const c_void, + align_items: *const c_void, + align_self: *const c_void, + justify_content: *const c_void, + justify_items: *const c_void, + justify_self: *const c_void, + column_gap: *const c_void, + row_gap: *const c_void, + parent_payload: *const c_void, +) -> *const c_void { + use crate::style_value::StyleValueData; + + abort_on_panic(|| { + let keyword_code = |data: *const c_void, map: fn(u16) -> Option| -> Option { + match unsafe { data.cast::().as_ref() } { + Some(StyleValueData::Keyword { keyword }) => map(*keyword), + _ => None, + } + }; + let built = AlignmentValues { + flex_direction: keyword_code(flex_direction, crate::css_enums::keyword_to_flex_direction)?, + flex_wrap: keyword_code(flex_wrap, crate::css_enums::keyword_to_flex_wrap)?, + flex_basis: ComputedFlexBasis::from_data(flex_basis), + flex_grow, + flex_shrink, + order, + align_content: keyword_code(align_content, crate::css_enums::keyword_to_align_content)?, + align_items: keyword_code(align_items, crate::css_enums::keyword_to_align_items)?, + align_self: keyword_code(align_self, crate::css_enums::keyword_to_align_self)?, + justify_content: keyword_code(justify_content, crate::css_enums::keyword_to_justify_content)?, + justify_items: keyword_code(justify_items, crate::css_enums::keyword_to_justify_items)?, + justify_self: keyword_code(justify_self, crate::css_enums::keyword_to_justify_self)?, + column_gap: ComputedGap::from_data(column_gap), + row_gap: ComputedGap::from_data(row_gap), + }; + + if !parent_payload.is_null() && built.eq(unsafe { &*parent_payload.cast::() }) { + retain_group_payload(group_index, parent_payload); + return Some(parent_payload); + } + let default_payload = default_group_payload(group_index); + if built.eq(unsafe { &*default_payload.cast::() }) { + return Some(default_payload); + } + + let payload = allocate_payload(vtable(group_index), 1); + unsafe { payload.cast::().write(built) }; + Some(payload.cast_const()) + }) + .unwrap_or(std::ptr::null()) +} + +/// Builds the complete non-inherited SVG geometry and painting group from its +/// computed property values. +/// +/// The returned payload carries one reference for the caller; fresh payloads +/// start at one, shared payloads are retained, and default payloads are +/// intentionally leaked and never counted. +/// +/// # Safety +/// The value pointers must identify valid StyleValueData, and +/// `parent_payload` must identify an SVG reset payload or be null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_build_svg_reset_group( + group_index: usize, + cx: *const c_void, + cy: *const c_void, + r: *const c_void, + rx: *const c_void, + ry: *const c_void, + x: *const c_void, + y: *const c_void, + stop_color: u32, + stop_opacity: f32, + flood_color: u32, + flood_opacity: f32, + vector_effect: *const c_void, + shape_rendering: *const c_void, + parent_payload: *const c_void, +) -> *const c_void { + use crate::style_value::StyleValueData; + + abort_on_panic(|| { + let keyword_code = |data: *const c_void, map: fn(u16) -> Option| -> Option { + match unsafe { data.cast::().as_ref() } { + Some(StyleValueData::Keyword { keyword }) => map(*keyword), + _ => None, + } + }; + let retained = |data: *const c_void| ComputedStyleValueHandle::retained(data.cast()); + let built = SVGResetValues { + cx: retained(cx), + cy: retained(cy), + r: retained(r), + rx: ComputedLengthPercentageOrAuto::from_data(rx), + ry: ComputedLengthPercentageOrAuto::from_data(ry), + x: retained(x), + y: retained(y), + stop_color, + stop_opacity, + flood_color, + flood_opacity, + vector_effect: keyword_code(vector_effect, crate::css_enums::keyword_to_vector_effect)?, + shape_rendering: keyword_code(shape_rendering, crate::css_enums::keyword_to_shape_rendering)?, + }; + + if !parent_payload.is_null() && built.eq(unsafe { &*parent_payload.cast::() }) { + retain_group_payload(group_index, parent_payload); + return Some(parent_payload); + } + let default_payload = default_group_payload(group_index); + if built.eq(unsafe { &*default_payload.cast::() }) { + return Some(default_payload); + } + + let payload = allocate_payload(vtable(group_index), 1); + unsafe { payload.cast::().write(built) }; + Some(payload.cast_const()) + }) + .unwrap_or(std::ptr::null()) +} + +/// Builds the complete surround group from the physical inset, margin, and +/// padding properties. Anchor insets retain their original value separately +/// while exposing auto through the length-box facade, matching layout's +/// existing representation. +/// +/// # Safety +/// Each value pointer must address valid StyleValueData, and `parent_payload` +/// must be a valid surround payload or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_build_surround_group( + group_index: usize, + top: *const c_void, + right: *const c_void, + bottom: *const c_void, + left: *const c_void, + margin_top: *const c_void, + margin_right: *const c_void, + margin_bottom: *const c_void, + margin_left: *const c_void, + padding_top: *const c_void, + padding_right: *const c_void, + padding_bottom: *const c_void, + padding_left: *const c_void, + parent_payload: *const c_void, +) -> *const c_void { + use crate::style_value::StyleValueData; + + abort_on_panic(|| { + let anchor = |data: *const c_void| { + let data = data.cast::(); + if matches!(unsafe { data.as_ref() }, Some(StyleValueData::Anchor { .. })) { + ComputedStyleValueHandle::retained(data) + } else { + ComputedStyleValueHandle::empty() + } + }; + let built = SurroundValues { + inset: ComputedLengthBox::from_data(top, right, bottom, left, true), + top_anchor_inset: anchor(top), + right_anchor_inset: anchor(right), + bottom_anchor_inset: anchor(bottom), + left_anchor_inset: anchor(left), + margin: ComputedLengthBox::from_data(margin_top, margin_right, margin_bottom, margin_left, false), + padding: ComputedLengthBox::from_data(padding_top, padding_right, padding_bottom, padding_left, false), + }; + + if !parent_payload.is_null() && built.eq(unsafe { &*parent_payload.cast::() }) { + retain_group_payload(group_index, parent_payload); + return Some(parent_payload); + } + let default_payload = default_group_payload(group_index); + if built.eq(unsafe { &*default_payload.cast::() }) { + return Some(default_payload); + } + + let payload = allocate_payload(vtable(group_index), 1); + unsafe { payload.cast::().write(built) }; + Some(payload.cast_const()) + }) + .unwrap_or(std::ptr::null()) +} + +/// Builds the complete sizing group from its six computed values. Accepted +/// sizing functions are already constrained to fit-content() by parsing, so +/// the function payload can be consumed without inspecting or copying its +/// interned name. +/// +/// # Safety +/// Each value pointer must address valid StyleValueData, and `parent_payload` +/// must be a valid sizing payload or null. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_build_sizing_group( + group_index: usize, + width: *const c_void, + min_width: *const c_void, + max_width: *const c_void, + height: *const c_void, + min_height: *const c_void, + max_height: *const c_void, + parent_payload: *const c_void, +) -> *const c_void { + abort_on_panic(|| { + let built = SizingValues { + width: ComputedSize::from_data(width), + min_width: ComputedSize::from_data(min_width), + max_width: ComputedSize::from_data(max_width), + height: ComputedSize::from_data(height), + min_height: ComputedSize::from_data(min_height), + max_height: ComputedSize::from_data(max_height), + }; + + if !parent_payload.is_null() && built.eq(unsafe { &*(parent_payload as *const SizingValues) }) { + retain_group_payload(group_index, parent_payload); + return parent_payload; + } + + let default_payload = default_group_payload(group_index); + if built.eq(unsafe { &*(default_payload as *const SizingValues) }) { + return default_payload; + } + + let payload = allocate_payload(vtable(group_index), 1); + unsafe { (payload as *mut SizingValues).write(built) }; + payload + }) +} + /// Builds an inherited table group payload from the computed values, with the /// same sharing rules as the inherited box builder. Border-spacing must be an /// absolute pixel length; two-value spacings and anything else fall back to @@ -658,7 +1501,7 @@ pub unsafe extern "C" fn rust_build_inherited_table_group( /// The enum fields follow the opaque-byte convention; the border spacings are /// raw CSSPixels fixed-point values. #[repr(C)] -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct InheritedTableValues { pub border_collapse: u8, pub caption_side: u8, @@ -667,6 +1510,34 @@ pub struct InheritedTableValues { pub border_spacing_vertical: i32, } +impl InheritedTableValues { + fn initial() -> Self { + use crate::css_enums::{border_collapse, caption_side, empty_cells}; + + Self { + border_collapse: border_collapse::SEPARATE, + caption_side: caption_side::TOP, + empty_cells: empty_cells::SHOW, + border_spacing_horizontal: 0, + border_spacing_vertical: 0, + } + } +} + +impl InheritedBoxValues { + fn initial() -> Self { + use crate::css_enums::{content_visibility, direction, image_rendering, visibility, writing_mode}; + + Self { + visibility: visibility::VISIBLE, + direction: direction::LTR, + writing_mode: writing_mode::HORIZONTAL_TB, + content_visibility: content_visibility::VISIBLE, + image_rendering: image_rendering::AUTO, + } + } +} + /// Returns the typed view of an inherited table group payload. /// /// # Safety @@ -689,6 +1560,42 @@ pub unsafe extern "C" fn rust_style_group_as_inherited_box(payload: *const c_voi payload as *const InheritedBoxValues } +/// Returns the typed view of a sizing group payload. +/// +/// # Safety +/// `payload` must be a sizing group payload. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_style_group_as_sizing(payload: *const c_void) -> *const SizingValues { + payload as *const SizingValues +} + +/// Returns the typed view of an alignment group payload. +/// +/// # Safety +/// `payload` must be an alignment group payload. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_style_group_as_alignment(payload: *const c_void) -> *const AlignmentValues { + payload as *const AlignmentValues +} + +/// Returns the typed view of an SVG reset group payload. +/// +/// # Safety +/// `payload` must be an SVG reset group payload. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_style_group_as_svg_reset(payload: *const c_void) -> *const SVGResetValues { + payload as *const SVGResetValues +} + +/// Returns the typed view of a surround group payload. +/// +/// # Safety +/// `payload` must be a surround group payload. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_style_group_as_surround(payload: *const c_void) -> *const SurroundValues { + payload as *const SurroundValues +} + #[cfg(test)] mod tests { use super::*; @@ -713,17 +1620,74 @@ mod tests { #[test] fn payload_lifecycle() { - let vtables = [StyleGroupVTable { - size: size_of::(), - align: align_of::(), - default_construct: test_default_construct, - copy_construct: test_copy_construct, - destruct: test_destruct, - equals: test_equals, - }]; - let mut defaults = [std::ptr::null::(); 1]; + let vtables = [ + StyleGroupVTable { + lifecycle: StyleGroupLifecycle::Cpp, + size: size_of::(), + align: align_of::(), + default_construct: Some(test_default_construct), + copy_construct: Some(test_copy_construct), + destruct: Some(test_destruct), + equals: Some(test_equals), + }, + StyleGroupVTable { + lifecycle: StyleGroupLifecycle::InheritedTable, + size: size_of::(), + align: align_of::(), + default_construct: None, + copy_construct: None, + destruct: None, + equals: None, + }, + StyleGroupVTable { + lifecycle: StyleGroupLifecycle::InheritedBox, + size: size_of::(), + align: align_of::(), + default_construct: None, + copy_construct: None, + destruct: None, + equals: None, + }, + StyleGroupVTable { + lifecycle: StyleGroupLifecycle::Sizing, + size: size_of::(), + align: align_of::(), + default_construct: None, + copy_construct: None, + destruct: None, + equals: None, + }, + StyleGroupVTable { + lifecycle: StyleGroupLifecycle::Alignment, + size: size_of::(), + align: align_of::(), + default_construct: None, + copy_construct: None, + destruct: None, + equals: None, + }, + StyleGroupVTable { + lifecycle: StyleGroupLifecycle::SVGReset, + size: size_of::(), + align: align_of::(), + default_construct: None, + copy_construct: None, + destruct: None, + equals: None, + }, + StyleGroupVTable { + lifecycle: StyleGroupLifecycle::Surround, + size: size_of::(), + align: align_of::(), + default_construct: None, + copy_construct: None, + destruct: None, + equals: None, + }, + ]; + let mut defaults = [std::ptr::null::(); 7]; unsafe { - rust_style_group_registry_register(vtables.as_ptr(), 1, defaults.as_mut_ptr()); + rust_style_group_registry_register(vtables.as_ptr(), vtables.len(), defaults.as_mut_ptr()); let default_payload = defaults[0]; assert_eq!(*(default_payload as *const u64), 7); assert_eq!( @@ -739,6 +1703,48 @@ mod tests { refcount.store(0, Ordering::Relaxed); rust_style_group_free(0, clone); assert_eq!(LIVE.load(Ordering::Relaxed), 1); + + let table_default = *(defaults[1] as *const InheritedTableValues); + assert_eq!(table_default, InheritedTableValues::initial()); + let table_clone = rust_style_group_clone(1, defaults[1]); + assert_eq!(*(table_clone as *const InheritedTableValues), table_default); + refcount_of(table_clone, align_of::()).store(0, Ordering::Relaxed); + rust_style_group_free(1, table_clone); + + let box_default = *(defaults[2] as *const InheritedBoxValues); + assert_eq!(box_default, InheritedBoxValues::initial()); + let box_clone = rust_style_group_clone(2, defaults[2]); + assert_eq!(*(box_clone as *const InheritedBoxValues), box_default); + refcount_of(box_clone, align_of::()).store(0, Ordering::Relaxed); + rust_style_group_free(2, box_clone); + + let sizing_default = &*(defaults[3] as *const SizingValues); + assert!(sizing_default.eq(&SizingValues::initial())); + let sizing_clone = rust_style_group_clone(3, defaults[3]); + assert!((*(sizing_clone as *const SizingValues)).eq(sizing_default)); + refcount_of(sizing_clone, align_of::()).store(0, Ordering::Relaxed); + rust_style_group_free(3, sizing_clone); + + let alignment_default = &*(defaults[4] as *const AlignmentValues); + assert!(alignment_default.eq(&AlignmentValues::initial())); + let alignment_clone = rust_style_group_clone(4, defaults[4]); + assert!((*(alignment_clone as *const AlignmentValues)).eq(alignment_default)); + refcount_of(alignment_clone, align_of::()).store(0, Ordering::Relaxed); + rust_style_group_free(4, alignment_clone); + + let svg_reset_default = &*(defaults[5] as *const SVGResetValues); + assert!(svg_reset_default.eq(&SVGResetValues::initial())); + let svg_reset_clone = rust_style_group_clone(5, defaults[5]); + assert!((*(svg_reset_clone as *const SVGResetValues)).eq(svg_reset_default)); + refcount_of(svg_reset_clone, align_of::()).store(0, Ordering::Relaxed); + rust_style_group_free(5, svg_reset_clone); + + let surround_default = &*(defaults[6] as *const SurroundValues); + assert!(surround_default.eq(&SurroundValues::initial())); + let surround_clone = rust_style_group_clone(6, defaults[6]); + assert!((*(surround_clone as *const SurroundValues)).eq(surround_default)); + refcount_of(surround_clone, align_of::()).store(0, Ordering::Relaxed); + rust_style_group_free(6, surround_clone); } } } diff --git a/Libraries/LibWeb/CSS/Rust/src/custom_properties.rs b/Libraries/LibWeb/CSS/Rust/src/custom_properties.rs index f10c5e00ac343..0db30cc2bf920 100644 --- a/Libraries/LibWeb/CSS/Rust/src/custom_properties.rs +++ b/Libraries/LibWeb/CSS/Rust/src/custom_properties.rs @@ -16,7 +16,7 @@ use crate::abort_on_panic; use crate::css_tokenizer::OwnedToken; use crate::css_tokenizer::OwnedTokenKind; use crate::css_tokenizer::tokenize_owned; -use crate::style_value::RetainedStyleValue; +use crate::style_value::RetainedStyleValueData; use crate::style_value::RetainedUtf16FlyString; use crate::style_value::StyleValueData; @@ -26,15 +26,13 @@ pub struct FfiCustomPropertyStoreEntry { pub name_utf8: *const u8, pub name_utf8_length: usize, pub important: bool, - pub shell: *const c_void, pub data: *const c_void, } struct CustomPropertyEntry { _name: RetainedUtf16FlyString, - _value: RetainedStyleValue, + value: RetainedStyleValueData, important: bool, - data: *const c_void, } pub struct CustomPropertyStore { @@ -190,7 +188,7 @@ fn resolve_custom_property( TokenResolution::Resolved(tokenize_owned(source)) }); }; - let data = unsafe { &*entry.data.cast::() }; + let data = entry.value.data(); if matches!(data, StyleValueData::GuaranteedInvalid) { return TokenResolution::Invalid; } @@ -550,8 +548,8 @@ pub unsafe extern "C" fn rust_custom_property_registry_destroy(registry: *mut c_ abort_on_panic(|| drop(unsafe { Box::from_raw(registry.cast::()) })); } -/// Creates one Rust store node. Each entry name transfers one leaked fly-string reference; -/// value shells are borrowed and retained by Rust. The parent is another Rc raw pointer. +/// Creates one Rust store node. Each entry transfers a leaked fly-string reference and a +/// strong style value data handle. The parent is another Rc raw pointer. /// /// # Safety /// `entries` must point at `entry_count` valid entries and `parent` must be null or a pointer @@ -588,9 +586,8 @@ pub unsafe extern "C" fn rust_custom_property_store_create( entry.name_raw, CustomPropertyEntry { _name: unsafe { RetainedUtf16FlyString::from_leaked_raw(entry.name_raw) }, - _value: unsafe { RetainedStyleValue::from_borrowed_shell_pointer(entry.shell) }, + value: unsafe { RetainedStyleValueData::from_retained_pointer(entry.data.cast()) }, important: entry.important, - data: entry.data, }, ) }) @@ -619,7 +616,6 @@ pub unsafe extern "C" fn rust_custom_property_store_destroy(store: *const c_void pub struct FfiCustomPropertyStoreValue { pub found: bool, pub important: bool, - pub shell: *const c_void, pub data: *const c_void, pub token_source: *const u8, pub token_source_length: usize, @@ -641,20 +637,16 @@ pub unsafe extern "C" fn rust_custom_property_store_get( return FfiCustomPropertyStoreValue { found: false, important: false, - shell: std::ptr::null(), data: std::ptr::null(), token_source: std::ptr::null(), token_source_length: 0, }; }; - let token_source = unsafe { &*entry.data.cast::() } - .unresolved_token_source() - .unwrap_or_default(); + let token_source = entry.value.data().unresolved_token_source().unwrap_or_default(); FfiCustomPropertyStoreValue { found: true, important: entry.important, - shell: entry._value.shell_pointer(), - data: entry.data, + data: entry.value.data() as *const StyleValueData as *const c_void, token_source: token_source.as_ptr(), token_source_length: token_source.len(), } diff --git a/Libraries/LibWeb/CSS/Rust/src/ffi_stats.rs b/Libraries/LibWeb/CSS/Rust/src/ffi_stats.rs index 1d7ff6b57ef66..0f0252c3cbed8 100644 --- a/Libraries/LibWeb/CSS/Rust/src/ffi_stats.rs +++ b/Libraries/LibWeb/CSS/Rust/src/ffi_stats.rs @@ -54,27 +54,21 @@ define_ffi_ops! { StyleValueQueryEntry => "styleValueQueryEntries", StyleGroupCloneEntry => "styleGroupCloneEntries", StyleGroupFreeEntry => "styleGroupFreeEntries", + AnimationEvaluationEntry => "animationEvaluationEntries", + TransitionDecisionEntry => "transitionDecisionEntries", // Callbacks: Rust -> C++. SelectorDomReadCallback => "selectorDomReadCallbacks", SelectorMetadataCallback => "selectorMetadataCallbacks", - CascadePropertyDisallowedCallback => "cascadePropertyDisallowedCallbacks", CascadeResolveUnresolvedCallback => "cascadeResolveUnresolvedCallbacks", CascadeParseSubstitutedCallback => "cascadeParseSubstitutedCallbacks", - CascadeDataOfCallback => "cascadeDataOfCallbacks", - CascadePendingSubstitutionCallback => "cascadePendingSubstitutionCallbacks", CascadeSourceSlotCallback => "cascadeSourceSlotCallbacks", CascadeCustomPropertyBatchCallback => "cascadeCustomPropertyBatchCallbacks", ShorthandSetLonghandCallback => "shorthandSetLonghandCallbacks", LonghandStoreBatchCallback => "longhandStoreBatchCallbacks", LonghandCppComputeFallback => "longhandCppComputeFallbacks", - LonghandContextFetchCallback => "longhandContextFetchCallbacks", LonghandParentValueFetchCallback => "longhandParentValueFetchCallbacks", - LonghandIndependenceFallbackCallback => "longhandIndependenceFallbackCallbacks", - LonghandWritingModeCallback => "longhandWritingModeCallbacks", - CalcSerializationCallback => "calcSerializationCallbacks", - StyleValueShellRetainCallback => "styleValueShellRetainCallbacks", - StyleValueShellReleaseCallback => "styleValueShellReleaseCallbacks", StringRetainReleaseCallback => "stringRetainReleaseCallbacks", + AnimationComputeBatchCallback => "animationComputeBatchCallbacks", } static COUNTERS: [AtomicU64; FFI_OP_COUNT] = [const { AtomicU64::new(0) }; FFI_OP_COUNT]; @@ -116,3 +110,13 @@ pub extern "C" fn rust_style_ffi_counters_reset() { pub extern "C" fn rust_style_ffi_note_style_value_created() { bump(FfiOp::StyleValueCreateEntry); } + +#[unsafe(no_mangle)] +pub extern "C" fn rust_style_ffi_note_animation_evaluation() { + bump(FfiOp::AnimationEvaluationEntry); +} + +#[unsafe(no_mangle)] +pub extern "C" fn rust_style_ffi_note_transition_decision() { + bump(FfiOp::TransitionDecisionEntry); +} diff --git a/Libraries/LibWeb/CSS/Rust/src/lib.rs b/Libraries/LibWeb/CSS/Rust/src/lib.rs index 44306027de2d0..d3ac08ea5af93 100644 --- a/Libraries/LibWeb/CSS/Rust/src/lib.rs +++ b/Libraries/LibWeb/CSS/Rust/src/lib.rs @@ -8,8 +8,11 @@ #[path = "../../../../RustAllocator.rs"] mod rust_allocator; +pub mod animation; pub mod calc; pub mod cascaded_properties; +mod color_conversion; +pub mod color_interpolation; pub mod computed_values; pub mod css_enums; pub mod css_pixels; @@ -22,6 +25,7 @@ pub mod property_metadata; mod selector_engine; pub mod style_compute; mod style_value; +pub mod transition; use std::panic::AssertUnwindSafe; use std::panic::catch_unwind; diff --git a/Libraries/LibWeb/CSS/Rust/src/property_metadata.rs b/Libraries/LibWeb/CSS/Rust/src/property_metadata.rs index 0fd657357fb03..2be22aec73279 100644 --- a/Libraries/LibWeb/CSS/Rust/src/property_metadata.rs +++ b/Libraries/LibWeb/CSS/Rust/src/property_metadata.rs @@ -40,6 +40,35 @@ pub fn property_requires_computation_level(property_id: u16) -> u8 { REQUIRES_COMPUTATION_LEVELS[longhand_index(property_id)] } +pub fn property_animation_type(property_id: u16) -> u8 { + PROPERTY_ANIMATION_TYPES[longhand_index(property_id)] +} + +pub(crate) fn pseudo_element_supports_property(pseudo_element: u8, property_id: u16) -> bool { + if PSEUDO_ELEMENT_ALWAYS_ALLOWED_PROPERTIES + .binary_search(&property_id) + .is_ok() + { + return true; + } + match PSEUDO_ELEMENT_PROPERTY_WHITELISTS[pseudo_element as usize] { + Some(whitelist) => whitelist.binary_search(&property_id).is_ok(), + None => true, + } +} + +/// An accepted numeric range for one CSS value type. +#[repr(C)] +pub struct FfiPropertyNumericRange { + pub value_type: u8, + pub min: f64, + pub max: f64, +} + +pub fn property_numeric_ranges(property_id: u16) -> &'static [FfiPropertyNumericRange] { + PROPERTY_NUMERIC_RANGES[longhand_index(property_id)] +} + /// Returns the longhand property identifiers in computation order. pub fn property_computation_order() -> &'static [u16] { &PROPERTY_COMPUTATION_ORDER @@ -65,6 +94,23 @@ pub extern "C" fn rust_property_metadata_requires_computation_level(property_id: property_requires_computation_level(property_id) } +#[unsafe(no_mangle)] +pub extern "C" fn rust_property_metadata_animation_type(property_id: u16) -> u8 { + property_animation_type(property_id) +} + +/// # Safety +/// `out_length` must be a valid pointer. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_property_metadata_numeric_ranges( + property_id: u16, + out_length: *mut usize, +) -> *const FfiPropertyNumericRange { + let ranges = property_numeric_ranges(property_id); + unsafe { *out_length = ranges.len() }; + ranges.as_ptr() +} + /// # Safety /// All out-pointers must be valid. #[unsafe(no_mangle)] @@ -94,6 +140,54 @@ pub fn longhands_for_shorthand(property_id: u16) -> &'static [u16] { SHORTHAND_EXPANSIONS[(property_id - FIRST_SHORTHAND_PROPERTY_ID) as usize] } +fn property_index(property_id: u16) -> usize { + debug_assert!((FIRST_SHORTHAND_PROPERTY_ID..=LAST_LONGHAND_PROPERTY_ID).contains(&property_id)); + (property_id - FIRST_SHORTHAND_PROPERTY_ID) as usize +} + +fn property_is_logical_alias_including_shorthands(property_id: u16) -> bool { + PROPERTY_IS_LOGICAL_ALIAS[property_index(property_id)] +} + +/// Returns whether `a` wins a keyframe declaration conflict with `b`. +pub(crate) fn animation_property_is_preferred(a: u16, b: u16) -> bool { + // https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes + // If conflicts arise when expanding shorthand properties or replacing logical properties with physical properties, apply the following rules in order until the conflict is resolved: + + // 1. Longhand properties override shorthand properties (e.g. border-top-color overrides border-top). + if property_is_shorthand(a) != property_is_shorthand(b) { + return !property_is_shorthand(a); + } + + // 2. Shorthand properties with fewer longhand components override those with more longhand components (e.g. border-top overrides border-color). + if property_is_shorthand(a) { + let a_length = SHORTHAND_EXPANDED_LONGHAND_COUNTS[(a - FIRST_SHORTHAND_PROPERTY_ID) as usize]; + let b_length = SHORTHAND_EXPANDED_LONGHAND_COUNTS[(b - FIRST_SHORTHAND_PROPERTY_ID) as usize]; + if a_length != b_length { + return a_length < b_length; + } + } + + let a_is_logical_alias = property_is_logical_alias_including_shorthands(a); + let b_is_logical_alias = property_is_logical_alias_including_shorthands(b); + + // 3. Physical properties override logical properties. + if a_is_logical_alias != b_is_logical_alias { + return !a_is_logical_alias; + } + + // 4. For shorthand properties with an equal number of longhand components, properties whose IDL name (see + // the CSS property to IDL attribute algorithm [CSSOM]) appears earlier when sorted in ascending order + // by the Unicode codepoints that make up each IDL name, override those who appear later. + PROPERTY_IDL_NAMES[property_index(a)] < PROPERTY_IDL_NAMES[property_index(b)] +} + +/// FFI accessor for the metadata parity test on the C++ side. +#[unsafe(no_mangle)] +pub extern "C" fn rust_animation_property_is_preferred(a: u16, b: u16) -> bool { + animation_property_is_preferred(a, b) +} + /// FFI accessors for the parity test on the C++ side. #[unsafe(no_mangle)] pub extern "C" fn rust_property_metadata_is_shorthand(property_id: u16) -> bool { diff --git a/Libraries/LibWeb/CSS/Rust/src/style_compute.rs b/Libraries/LibWeb/CSS/Rust/src/style_compute.rs index 06f6d51222036..9e966d3564873 100644 --- a/Libraries/LibWeb/CSS/Rust/src/style_compute.rs +++ b/Libraries/LibWeb/CSS/Rust/src/style_compute.rs @@ -16,7 +16,7 @@ //! the C++ caller falls back to its own resolution. use std::ffi::c_void; -use std::sync::OnceLock; +use std::sync::{Arc, OnceLock}; use crate::abort_on_panic; use crate::cascaded_properties::CascadedPropertyStore; @@ -25,7 +25,7 @@ use crate::display::FfiDisplay; use crate::property_metadata::longhands_for_shorthand; use crate::property_metadata::property_is_inherited; use crate::property_metadata::property_is_shorthand; -use crate::style_value::StyleValueData; +use crate::style_value::{GridTrackEntryKind, RetainedStyleValueData, RetainedStyleValueDataList, StyleValueData}; pub use crate::css_enums::*; @@ -100,6 +100,22 @@ pub(crate) fn px_length_unit() -> u8 { *PX.get_or_init(|| LENGTH_UNIT_NAMES.iter().position(|&name| name == "px").unwrap() as u8) } +pub(crate) fn none_keyword() -> u16 { + keyword::NONE +} + +pub(crate) fn current_color_keyword() -> u16 { + keyword::CURRENTCOLOR +} + +pub(crate) fn absolute_length_to_px(value: f64, unit: u8) -> Option { + match length_unit_kinds().get(unit as usize)? { + LengthUnitKind::Px => Some(value), + LengthUnitKind::Absolute { px_per_unit } => Some(value * px_per_unit), + _ => None, + } +} + fn length_unit_kinds() -> &'static [LengthUnitKind] { static KINDS: OnceLock> = OnceLock::new(); KINDS.get_or_init(|| { @@ -765,57 +781,57 @@ pub unsafe extern "C" fn rust_pseudo_element_content_bails(content_value: *const }) } -/// Whether a value is computationally independent, when the decision is -/// available in the core; `handled` is false for value types whose rule still -/// lives with their C++ shells. -#[repr(C)] -pub struct FfiIndependenceDecision { - pub handled: bool, - pub independent: bool, -} - /// https://drafts.css-houdini.org/css-properties-values-api/#computationally-independent /// A property value is computationally independent if it can be converted into a computed value /// using only the value of the property on the element, and "global" information that cannot be /// changed by CSS. /// -/// Returns None for value types whose rule still lives with their C++ shells; a container is -/// also undecided when any of its nested values is, so the whole tree falls back. -fn value_is_computationally_independent( - value: &StyleValueData, - data_of: unsafe extern "C" fn(*const c_void) -> *const c_void, - decide_fallback: unsafe extern "C" fn(*const c_void) -> bool, -) -> Option { - use crate::style_value::RetainedStyleValue; - // An absent nested value never makes its container dependent. A nested value the core - // cannot decide is decided by the C++ fallback in place, so containers never go - // unhandled; only root values fall back. - let child = |retained: &RetainedStyleValue| -> Option { - let shell = retained.shell_pointer(); - if shell.is_null() { - return Some(true); - } - let data = unsafe { data_of(shell) }; - match value_is_computationally_independent( - unsafe { &*(data as *const StyleValueData) }, - data_of, - decide_fallback, - ) { - Some(independent) => Some(independent), - None => Some(unsafe { decide_fallback(shell) }), +/// Returns None for value types that must not reach computational-independence checks. A +/// container is likewise undecided when any nested value is unsupported. +fn value_is_computationally_independent(value: &StyleValueData) -> Option { + fn grid_entries_are_computationally_independent( + entries: &[crate::style_value::RetainedGridTrackEntry], + ) -> Option { + for entry in entries { + let independent = match entry.kind { + // A line-name entry carries no style value. + GridTrackEntryKind::LineNames => true, + // A single track size. + GridTrackEntryKind::Size => value_is_computationally_independent(entry.size_value.data())?, + // A minmax() track size. + GridTrackEntryKind::MinMax => { + value_is_computationally_independent(entry.min_value.data())? + && value_is_computationally_independent(entry.max_value.data())? + } + // A repeat() track and its optional fixed repeat count. + GridTrackEntryKind::Repeat => { + grid_entries_are_computationally_independent(entry.repeat_entries())? + && match entry.repeat_count.optional_data() { + Some(count) => value_is_computationally_independent(count)?, + None => true, + } + } + }; + if !independent { + return Some(false); + } } - }; - let all_of = |children: &[&RetainedStyleValue]| -> Option { + Some(true) + } + + let all_data_in_list = |list: &crate::style_value::RetainedStyleValueDataList| -> Option { let mut independent = true; - for retained in children { - independent = independent && child(retained)?; + for retained in list.as_slice() { + if let Some(data) = retained.optional_data() { + independent = independent && value_is_computationally_independent(data)?; + } } Some(independent) }; - let all_in_list = |list: &crate::style_value::RetainedStyleValueList| -> Option { + let all_data = |children: &[&crate::style_value::RetainedStyleValueData]| -> Option { let mut independent = true; - for retained in list.as_slice() { - independent = independent && child(retained)?; + for retained in children { + independent = independent && value_is_computationally_independent(retained.data())?; } Some(independent) }; @@ -864,31 +880,26 @@ fn value_is_computationally_independent( StyleValueData::Calculated { rust_calculation, .. } => { Some(rust_calculation.node().is_computationally_independent( &|unit| !length_unit_is_font_or_container_relative(unit), - &|retained| { - let shell = retained.shell_pointer(); - if shell.is_null() { - return true; - } - let data = unsafe { data_of(shell) }; - match value_is_computationally_independent( - unsafe { &*(data as *const StyleValueData) }, - data_of, - decide_fallback, - ) { - Some(independent) => independent, - None => unsafe { decide_fallback(shell) }, - } - }, + &|retained| value_is_computationally_independent(retained.data()).unwrap_or_default(), )) } StyleValueData::Ratio { numerator, denominator, .. - } => all_of(&[numerator, denominator]), - StyleValueData::Edge { offset, .. } => child(offset), - StyleValueData::Function { value, .. } => child(value), - StyleValueData::OpacityValue { value } => child(value), + } => all_data(&[numerator, denominator]), + StyleValueData::Edge { offset, .. } => match offset.optional_data() { + Some(offset) => value_is_computationally_independent(offset), + None => Some(true), + }, + StyleValueData::Function { value, .. } => all_data(&[value]), + StyleValueData::OpacityValue { value } => all_data(&[value]), // Auto placements carry no value; spans and lines recurse into theirs. - StyleValueData::GridTrackPlacement { value, .. } => child(value), + StyleValueData::GridTrackPlacement { value, .. } => match value.optional_data() { + Some(value) => value_is_computationally_independent(value), + None => Some(true), + }, + StyleValueData::GridTrackSizeList { entries, .. } => { + grid_entries_are_computationally_independent(entries.as_slice()) + } // FIXME: Consider sub-values once we support values StyleValueData::ColorInterpolationMethod { .. } => Some(true), StyleValueData::ColorFunction { @@ -898,17 +909,47 @@ fn value_is_computationally_independent( alpha, origin_color, .. - } => all_of(&[channel_0, channel_1, channel_2, alpha, origin_color]), + } => { + let mut independent = true; + for value in [channel_0, channel_1, channel_2, alpha, origin_color] { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } + Some(independent) + } StyleValueData::BorderImageSlice { top, right, bottom, left, .. - } => all_of(&[top, right, bottom, left]), - StyleValueData::Content { content, alt_text } => all_of(&[content, alt_text]), + } => all_data(&[top, right, bottom, left]), + StyleValueData::Content { content, alt_text } => { + let mut independent = value_is_computationally_independent(content.data())?; + if let Some(alt_text) = alt_text.optional_data() { + independent = independent && value_is_computationally_independent(alt_text)?; + } + Some(independent) + } // Extent components carry no value; explicit sizes recurse into theirs. - StyleValueData::RadialSize { value_0, value_1, .. } => all_of(&[value_0, value_1]), + StyleValueData::RadialSize { + component_count, + is_extent_0, + value_0, + is_extent_1, + value_1, + .. + } => { + let mut independent = true; + if !is_extent_0 { + independent = value_is_computationally_independent(value_0.data())?; + } + if *component_count == 2 && !is_extent_1 { + independent = independent && value_is_computationally_independent(value_1.data())?; + } + Some(independent) + } // Every shape kind's rule is a conjunction over the values it uses; the // unused generic fields and point list of the other kinds are absent, so // one null-tolerant conjunction covers inset, xywh, rect, circle, @@ -922,18 +963,36 @@ fn value_is_computationally_independent( points, .. } => { - let mut independent = all_of(&[v0, v1, v2, v3, v4])?; + let mut independent = true; + for value in [v0, v1, v2, v3, v4] { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } for point in points.as_slice() { - independent = independent && all_of(&point.values())?; + for value in point.values() { + independent = independent && value_is_computationally_independent(value.data())?; + } } Some(independent) } // Every filter kind's rule recurses into its single value. - StyleValueData::Filter { value, .. } => child(value), - StyleValueData::Counter { counter_style, .. } => child(counter_style), - StyleValueData::OpenTypeTagged { value, .. } => child(value), - StyleValueData::RandomValueSharing { fixed_value, .. } => child(fixed_value), - StyleValueData::Cursor { image, x, y } => all_of(&[image, x, y]), + StyleValueData::Filter { value, .. } => all_data(&[value]), + StyleValueData::Counter { counter_style, .. } => all_data(&[counter_style]), + StyleValueData::OpenTypeTagged { value, .. } => all_data(&[value]), + StyleValueData::RandomValueSharing { fixed_value, .. } => match fixed_value.optional_data() { + Some(fixed_value) => value_is_computationally_independent(fixed_value), + None => Some(true), + }, + StyleValueData::Cursor { image, x, y } => { + let mut independent = value_is_computationally_independent(image.data())?; + for coordinate in [x, y] { + if let Some(coordinate) = coordinate.optional_data() { + independent = independent && value_is_computationally_independent(coordinate)?; + } + } + Some(independent) + } // The unused fields of the non-matching easing kinds are absent, so one // null-tolerant conjunction covers every kind's rule. StyleValueData::Easing { @@ -945,23 +1004,34 @@ fn value_is_computationally_independent( number_of_intervals, .. } => { - let mut independent = all_of(&[x1, y1, x2, y2, number_of_intervals])?; + let mut independent = true; + for value in [x1, y1, x2, y2, number_of_intervals] { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } for stop in linear_stops.as_slice() { - independent = independent && all_of(&stop.values())?; + for value in stop.values() { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } } Some(independent) } StyleValueData::ImageSet { options } => { let mut independent = true; for option in options.as_slice() { - independent = independent && all_of(&option.values())?; + independent = independent && all_data(&option.values())?; } Some(independent) } StyleValueData::CounterDefinitions { counter_definitions } => { let mut independent = true; for definition in counter_definitions.as_slice() { - independent = independent && child(definition.value())?; + if let Some(value) = definition.value().optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } } Some(independent) } @@ -971,9 +1041,18 @@ fn value_is_computationally_independent( color_interpolation_method, .. } => { - let mut independent = child(direction_value)? && child(color_interpolation_method)?; + let mut independent = true; + for value in [direction_value, color_interpolation_method] { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } for stop in color_stop_list.as_slice() { - independent = independent && all_of(&stop.values())?; + for value in stop.values() { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } } Some(independent) } @@ -984,9 +1063,18 @@ fn value_is_computationally_independent( color_interpolation_method, .. } => { - let mut independent = child(from_angle)? && child(position)? && child(color_interpolation_method)?; + let mut independent = true; + for value in [from_angle, position, color_interpolation_method] { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } for stop in color_stop_list.as_slice() { - independent = independent && all_of(&stop.values())?; + for value in stop.values() { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } } Some(independent) } @@ -997,31 +1085,43 @@ fn value_is_computationally_independent( color_interpolation_method, .. } => { - let mut independent = child(size)? && child(position)? && child(color_interpolation_method)?; + let mut independent = true; + for value in [size, position, color_interpolation_method] { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } for stop in color_stop_list.as_slice() { - independent = independent && all_of(&stop.values())?; + for value in stop.values() { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } } Some(independent) } - StyleValueData::ContrastColor { color, .. } => child(color), - StyleValueData::Superellipse { parameter } => child(parameter), + StyleValueData::ContrastColor { color, .. } => all_data(&[color]), + StyleValueData::Superellipse { parameter } => all_data(&[parameter]), StyleValueData::ScrollbarColor { thumb_color, track_color, .. - } => all_of(&[thumb_color, track_color]), + } => all_data(&[thumb_color, track_color]), StyleValueData::Rect { top, right, bottom, left, .. - } => all_of(&[top, right, bottom, left]), - StyleValueData::FontStyle { angle_value, .. } => child(angle_value), - StyleValueData::TextIndent { length_percentage, .. } => child(length_percentage), - StyleValueData::OverflowClipMargin { offset, .. } => child(offset), - StyleValueData::BackgroundSize { size_x, size_y, .. } => all_of(&[size_x, size_y]), - StyleValueData::Position { edge_x, edge_y, .. } => all_of(&[edge_x, edge_y]), + } => all_data(&[top, right, bottom, left]), + StyleValueData::FontStyle { angle_value, .. } => match angle_value.optional_data() { + Some(angle_value) => value_is_computationally_independent(angle_value), + None => Some(true), + }, + StyleValueData::TextIndent { length_percentage, .. } => all_data(&[length_percentage]), + StyleValueData::OverflowClipMargin { offset, .. } => all_data(&[offset]), + StyleValueData::BackgroundSize { size_x, size_y, .. } => all_data(&[size_x, size_y]), + StyleValueData::Position { edge_x, edge_y, .. } => all_data(&[edge_x, edge_y]), StyleValueData::Shadow { color, offset_x, @@ -1029,7 +1129,16 @@ fn value_is_computationally_independent( blur_radius, spread_distance, .. - } => all_of(&[color, offset_x, offset_y, blur_radius, spread_distance]), + } => { + let mut independent = value_is_computationally_independent(offset_x.data())? + && value_is_computationally_independent(offset_y.data())?; + for value in [color, blur_radius, spread_distance] { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } + Some(independent) + } StyleValueData::ColorMix { color_interpolation_method, first_color, @@ -1037,58 +1146,49 @@ fn value_is_computationally_independent( second_color, second_percentage, .. - } => all_of(&[ - color_interpolation_method, - first_color, - first_percentage, - second_color, - second_percentage, - ]), - StyleValueData::ValueList { values, .. } + } => { + let mut independent = true; + for value in [ + color_interpolation_method, + first_color, + first_percentage, + second_color, + second_percentage, + ] { + if let Some(value) = value.optional_data() { + independent = independent && value_is_computationally_independent(value)?; + } + } + Some(independent) + } + StyleValueData::Shorthand { values, .. } + | StyleValueData::ValueList { values, .. } | StyleValueData::Tuple { values } - | StyleValueData::Transformation { values, .. } - | StyleValueData::Shorthand { values, .. } => all_in_list(values), + | StyleValueData::Transformation { values, .. } => all_data_in_list(values), StyleValueData::BorderRadiusRect { top_left, top_right, bottom_right, bottom_left, .. - } => all_of(&[top_left, top_right, bottom_right, bottom_left]), + } => all_data(&[top_left, top_right, bottom_right, bottom_left]), StyleValueData::BorderRadius { horizontal_radius, vertical_radius, .. - } => all_of(&[horizontal_radius, vertical_radius]), + } => all_data(&[horizontal_radius, vertical_radius]), _ => None, } } /// # Safety -/// `data` must point at a valid StyleValueData and `data_of` must be a valid callback mapping a -/// nested value's shell pointer to its Rust-owned data. +/// `data` must point at a valid StyleValueData. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_style_value_is_computationally_independent( - data: *const c_void, - data_of: unsafe extern "C" fn(shell: *const c_void) -> *const c_void, - decide_fallback: unsafe extern "C" fn(shell: *const c_void) -> bool, -) -> FfiIndependenceDecision { +pub unsafe extern "C" fn rust_style_value_is_computationally_independent(data: *const c_void) -> bool { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StyleValueQueryEntry); abort_on_panic(|| { - match value_is_computationally_independent( - unsafe { &*(data as *const StyleValueData) }, - data_of, - decide_fallback, - ) { - Some(independent) => FfiIndependenceDecision { - handled: true, - independent, - }, - None => FfiIndependenceDecision { - handled: false, - independent: false, - }, - } + value_is_computationally_independent(unsafe { &*(data as *const StyleValueData) }) + .expect("computational independence requested for an unsupported value") }) } @@ -1330,16 +1430,12 @@ pub unsafe extern "C" fn rust_compute_corner_shape_parameter(absolutized_value: /// Whether a font-family value is a single monospace keyword, which triggers /// the monospace font-size recascade. The list entry's keyword is read through -/// the nested value's shell pointer. +/// the nested value's shared Rust data handle. /// /// # Safety -/// `data` must point at a valid StyleValueData and `data_of` map a nested -/// value's shell pointer to its Rust-owned data. +/// `data` must point at a valid StyleValueData. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_font_family_is_monospace( - data: *const c_void, - data_of: unsafe extern "C" fn(shell: *const c_void) -> *const c_void, -) -> bool { +pub unsafe extern "C" fn rust_font_family_is_monospace(data: *const c_void) -> bool { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::NestedPropertyComputeEntry); abort_on_panic(|| { let StyleValueData::ValueList { values, .. } = (unsafe { &*(data as *const StyleValueData) }) else { @@ -1349,51 +1445,54 @@ pub unsafe extern "C" fn rust_font_family_is_monospace( if values.len() != 1 { return false; } - let entry_data = unsafe { data_of(values[0].shell_pointer()) }; matches!( - unsafe { &*(entry_data as *const StyleValueData) }, + values[0].data(), StyleValueData::Keyword { keyword } if *keyword == keyword::MONOSPACE ) }) } -/// Computes the ordering of a font-feature-settings or font-variation-settings -/// value list: deduplicate by tag with the later occurrence taking precedence, -/// then sort the survivors ascending by tag. The tag comparisons run through -/// C++ callbacks over the entry indices, since the tags are interned fly -/// strings the core does not read directly. Writes the surviving original -/// indices in computed order to `out_indices` and returns their count. +/// Computes a font-feature-settings or font-variation-settings value list: +/// deduplicate by tag with the later occurrence taking precedence, then sort +/// the survivors ascending by tag. /// https://drafts.csswg.org/css-fonts/#font-feature-settings-prop /// /// # Safety -/// The callbacks must be valid and `out_indices` must have room for `count` -/// entries. +/// `data` must point at a value list of OpenType tagged values. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_font_feature_settings_computed_order( - count: usize, - context: *mut c_void, - tags_equal: unsafe extern "C" fn(*mut c_void, usize, usize) -> bool, - tag_less: unsafe extern "C" fn(*mut c_void, usize, usize) -> bool, - out_indices: *mut u32, -) -> usize { +#[allow(clippy::arc_with_non_send_sync)] +pub unsafe extern "C" fn rust_compute_font_feature_settings(data: *const c_void) -> *const c_void { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::NestedPropertyComputeEntry); abort_on_panic(|| { + let StyleValueData::ValueList { + values, + separator, + collapsible, + } = (unsafe { &*data.cast::() }) + else { + unreachable!("font feature settings must be a value list") + }; + let values = values.as_slice(); + let packed_tag = |index: usize| match values[index].data() { + StyleValueData::OpenTypeTagged { packed_tag, .. } => *packed_tag, + _ => unreachable!("font feature settings must contain OpenType tagged values"), + }; + // Keep the last occurrence of each tag; later declarations take precedence. - let mut survivors: Vec = (0..count) - .filter(|&i| !((i + 1)..count).any(|j| unsafe { tags_equal(context, i, j) })) + let mut survivors: Vec = (0..values.len()) + .filter(|&i| !((i + 1)..values.len()).any(|j| packed_tag(i) == packed_tag(j))) .collect(); - // The survivors have distinct tags, so tag_less is a total order over them. - survivors.sort_by(|&a, &b| { - if unsafe { tag_less(context, a, b) } { - std::cmp::Ordering::Less - } else { - std::cmp::Ordering::Greater - } - }); - for (slot, &index) in survivors.iter().enumerate() { - unsafe { *out_indices.add(slot) = index as u32 }; - } - survivors.len() + survivors.sort_unstable_by_key(|&index| packed_tag(index)); + let values = survivors + .into_iter() + .map(|index| values[index].clone_retained()) + .collect(); + Arc::into_raw(Arc::new(StyleValueData::ValueList { + values: crate::style_value::RetainedStyleValueDataList::from_retained_values(values), + separator: *separator, + collapsible: *collapsible, + })) + .cast() }) } @@ -1509,13 +1608,7 @@ fn compute_letter_or_word_spacing_value(absolutized_value: &StyleValueData) -> F } } -// https://drafts.csswg.org/css-anchor-position/#position-area-computed -// The computed value of a value is the two keywords indicating the selected -// tracks in each axis, with the long (block-start) and short (start) logical keywords treated -// as equivalent. It serializes with the logical keywords in their short forms. -#[unsafe(no_mangle)] -pub extern "C" fn rust_position_area_short_keyword(keyword: u16) -> u16 { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::NestedPropertyComputeEntry); +fn position_area_short_keyword(keyword: u16) -> u16 { match keyword { keyword::BLOCK_START | keyword::INLINE_START => keyword::START, keyword::BLOCK_END | keyword::INLINE_END => keyword::END, @@ -1529,81 +1622,88 @@ pub extern "C" fn rust_position_area_short_keyword(keyword: u16) -> u16 { } } -/// The outcome of the position-area span-all remapping: whether a single -/// keyword replaces the two-keyword value, and that keyword. -#[repr(C)] -pub struct FfiPositionAreaRemap { - pub remapped: bool, - pub keyword: u16, -} - -/// When one axis of a position-area value is span-all, the value computes to a -/// single logical keyword drawn from the other axis. Returns that keyword, or -/// reports that no span-all remapping applies (both axes are then serialized -/// in short form by the caller). -/// https://drafts.csswg.org/css-anchor-position/#position-area-computed -#[unsafe(no_mangle)] -pub extern "C" fn rust_position_area_span_all_remap(block_keyword: u16, inline_keyword: u16) -> FfiPositionAreaRemap { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::NestedPropertyComputeEntry); - let remapped = |keyword| FfiPositionAreaRemap { - remapped: true, - keyword, - }; - let not_remapped = FfiPositionAreaRemap { - remapped: false, - keyword: 0, - }; +fn position_area_span_all_remap(block_keyword: u16, inline_keyword: u16) -> Option { if block_keyword == keyword::SPAN_ALL { return match inline_keyword { - keyword::START => remapped(keyword::INLINE_START), - keyword::END => remapped(keyword::INLINE_END), - keyword::SELF_START => remapped(keyword::SELF_INLINE_START), - keyword::SELF_END => remapped(keyword::SELF_INLINE_END), - keyword::SPAN_START => remapped(keyword::SPAN_INLINE_START), - keyword::SPAN_END => remapped(keyword::SPAN_INLINE_END), - keyword::SPAN_SELF_START => remapped(keyword::SPAN_SELF_INLINE_START), - keyword::SPAN_SELF_END => remapped(keyword::SPAN_SELF_INLINE_END), - _ => not_remapped, + keyword::START => Some(keyword::INLINE_START), + keyword::END => Some(keyword::INLINE_END), + keyword::SELF_START => Some(keyword::SELF_INLINE_START), + keyword::SELF_END => Some(keyword::SELF_INLINE_END), + keyword::SPAN_START => Some(keyword::SPAN_INLINE_START), + keyword::SPAN_END => Some(keyword::SPAN_INLINE_END), + keyword::SPAN_SELF_START => Some(keyword::SPAN_SELF_INLINE_START), + keyword::SPAN_SELF_END => Some(keyword::SPAN_SELF_INLINE_END), + _ => None, }; } if inline_keyword == keyword::SPAN_ALL { return match block_keyword { - keyword::START => remapped(keyword::BLOCK_START), - keyword::END => remapped(keyword::BLOCK_END), - keyword::SELF_START => remapped(keyword::SELF_BLOCK_START), - keyword::SELF_END => remapped(keyword::SELF_BLOCK_END), - keyword::SPAN_START => remapped(keyword::SPAN_BLOCK_START), - keyword::SPAN_END => remapped(keyword::SPAN_BLOCK_END), - keyword::SPAN_SELF_START => remapped(keyword::SPAN_SELF_BLOCK_START), - keyword::SPAN_SELF_END => remapped(keyword::SPAN_SELF_BLOCK_END), - _ => not_remapped, + keyword::START => Some(keyword::BLOCK_START), + keyword::END => Some(keyword::BLOCK_END), + keyword::SELF_START => Some(keyword::SELF_BLOCK_START), + keyword::SELF_END => Some(keyword::SELF_BLOCK_END), + keyword::SPAN_START => Some(keyword::SPAN_BLOCK_START), + keyword::SPAN_END => Some(keyword::SPAN_BLOCK_END), + keyword::SPAN_SELF_START => Some(keyword::SPAN_SELF_BLOCK_START), + keyword::SPAN_SELF_END => Some(keyword::SPAN_SELF_BLOCK_END), + _ => None, }; } - not_remapped + None } -/// A style value crossing the FFI as its C++ shell pointer paired with its -/// Rust-owned data pointer. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct FfiShellAndData { - pub shell: *const c_void, - pub data: *const c_void, -} +// https://drafts.csswg.org/css-anchor-position/#position-area-computed +#[allow(clippy::arc_with_non_send_sync)] +fn compute_position_area(value: &StyleValueData) -> Option> { + // The computed value of a value is the two keywords indicating the selected tracks in each axis, + // with the long (block-start) and short (start) logical keywords treated as equivalent. It serializes in the order + // given in the grammar (above), with the logical keywords serialized in their short forms (e.g. start start + // instead of block-start inline-start). + let StyleValueData::ValueList { + values, + separator, + collapsible, + } = value + else { + return None; + }; + let [block_value, inline_value] = values.as_slice() else { + unreachable!("position-area must contain two keywords") + }; + let StyleValueData::Keyword { keyword: block_keyword } = block_value.data() else { + unreachable!("position-area must contain keywords") + }; + let StyleValueData::Keyword { + keyword: inline_keyword, + } = inline_value.data() + else { + unreachable!("position-area must contain keywords") + }; -impl FfiShellAndData { - pub const fn null() -> Self { - Self { - shell: std::ptr::null(), - data: std::ptr::null(), - } + if let Some(keyword) = position_area_span_all_remap(*block_keyword, *inline_keyword) { + return Some(Arc::new(StyleValueData::Keyword { keyword })); } + let short_block_keyword = position_area_short_keyword(*block_keyword); + let short_inline_keyword = position_area_short_keyword(*inline_keyword); + if short_block_keyword == *block_keyword && short_inline_keyword == *inline_keyword { + return None; + } + + let retained_keyword = |keyword| unsafe { + RetainedStyleValueData::from_retained_pointer(Arc::into_raw(Arc::new(StyleValueData::Keyword { keyword }))) + }; + Some(Arc::new(StyleValueData::ValueList { + values: RetainedStyleValueDataList::from_retained_values(vec![ + retained_keyword(short_block_keyword), + retained_keyword(short_inline_keyword), + ]), + separator: *separator, + collapsible: *collapsible, + })) } -/// The per-longhand initial values. The C++ side pins every entry for the -/// process lifetime before installing the table, so lookups never cross the -/// FFI and the pointers never dangle. -struct InitialValueTable(Vec); +/// The per-longhand initial values as shared Rust value identities. +struct InitialValueTable(Vec); // SAFETY: The entries reference immortal, immutable style values. unsafe impl Send for InitialValueTable {} @@ -1615,12 +1715,18 @@ static INITIAL_VALUE_TABLE: std::sync::OnceLock = std::sync:: /// order. /// /// # Safety -/// `entries` must point at `length` valid entries whose shells and data stay -/// alive for the process lifetime. +/// `entries` must point at `length` transferred strong references. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_style_metadata_set_initial_value_table(entries: *const FfiShellAndData, length: usize) { +pub unsafe extern "C" fn rust_style_metadata_set_initial_value_table(entries: *const *const c_void, length: usize) { abort_on_panic(|| { - let entries = unsafe { std::slice::from_raw_parts(entries, length) }.to_vec(); + let entries = unsafe { std::slice::from_raw_parts(entries, length) } + .iter() + .map(|entry| unsafe { + crate::style_value::RetainedStyleValueData::from_retained_pointer( + (*entry).cast::(), + ) + }) + .collect(); assert_eq!( length, crate::property_metadata::NUMBER_OF_LONGHAND_PROPERTIES, @@ -1633,17 +1739,17 @@ pub unsafe extern "C" fn rust_style_metadata_set_initial_value_table(entries: *c }); } -/// Returns the initial value of a longhand property. -pub(crate) fn initial_value(property_id: u16) -> FfiShellAndData { +/// Returns the initial value data of a longhand property. +pub(crate) fn initial_value_data(property_id: u16) -> *const crate::style_value::StyleValueData { use crate::property_metadata::FIRST_LONGHAND_PROPERTY_ID; let table = INITIAL_VALUE_TABLE.get().expect("initial value table not installed"); - table.0[(property_id - FIRST_LONGHAND_PROPERTY_ID) as usize] + table.0[(property_id - FIRST_LONGHAND_PROPERTY_ID) as usize].pointer() } /// FFI accessor for the parity test on the C++ side. #[unsafe(no_mangle)] -pub extern "C" fn rust_style_metadata_initial_value(property_id: u16) -> FfiShellAndData { - abort_on_panic(|| initial_value(property_id)) +pub extern "C" fn rust_style_metadata_initial_value(property_id: u16) -> *const c_void { + abort_on_panic(|| initial_value_data(property_id).cast()) } /// One bit per keyword marking the color keywords, installed once from the @@ -1808,7 +1914,7 @@ pub extern "C" fn rust_map_physical_to_logical_alias(property_id: u16, writing_m } /// One deferred store operation for a longhand whose selected value needs no -/// computation: the value shell and the flags driving the C++ side effects +/// computation: the value identity and the flags driving the C++ side effects /// (animated-inheritance copy and inheritance-dependent bookkeeping). #[repr(C)] pub struct FfiComputedStoreEntry { @@ -1816,18 +1922,25 @@ pub struct FfiComputedStoreEntry { pub inherited_property_id: u16, /// The selected specified value; also the stored value unless a computed /// pixel length or keyword replaces it. - pub shell: *const c_void, + pub data: *const c_void, + /// The C++ declaration-source slot for a cascaded value, or -1. + pub source_slot: i64, + /// Whether the selected cascaded facade carried stylesheet context. + pub has_style_sheet_context: bool, pub inheritance_dependent: bool, pub inherited: bool, - /// How the natively computed value crosses: with COMPUTED_KIND_SHELL the - /// stored value is `shell` itself; the other kinds carry a replacement in - /// `value` while `shell` remains the specified value for the + /// An owned replacement style value when `computed_kind` is + /// `COMPUTED_KIND_STYLE_VALUE`. The C++ callback adopts this reference. + pub computed_data: *const c_void, + /// How the natively computed value crosses: with COMPUTED_KIND_UNCHANGED + /// the stored value is `data` itself; the other kinds carry a replacement + /// in `value` while `data` remains the specified value for the /// inheritance-dependence bookkeeping. pub computed_kind: u8, pub value: f64, } -pub const COMPUTED_KIND_SHELL: u8 = 0; +pub const COMPUTED_KIND_UNCHANGED: u8 = 0; /// A pixel length of `value`. pub const COMPUTED_KIND_PX_LENGTH: u8 = 1; /// An integer of `value`. @@ -1849,6 +1962,44 @@ pub const COMPUTED_KIND_COMPUTE_IN_CPP: u8 = 7; pub const COMPUTED_KIND_KEYWORD: u8 = 8; /// A display value encoded as tag | first << 8 | second << 16 | third << 24. pub const COMPUTED_KIND_DISPLAY: u8 = 9; +/// A complete Rust-owned style value transferred through `computed_data`. +pub const COMPUTED_KIND_STYLE_VALUE: u8 = 10; + +pub const LONGHAND_BATCH_REQUEST_NONE: u8 = 0; +pub const LONGHAND_BATCH_REQUEST_LENGTH_CONTEXT: u8 = 1; +pub const LONGHAND_BATCH_REQUEST_PARENT_VALUE: u8 = 2; +pub const LONGHAND_BATCH_REQUEST_POST_COMPUTE_ADJUSTMENTS: u8 = 3; + +#[repr(C)] +pub struct FfiLonghandBatchRequest { + pub property_id: u16, + pub out_context: *mut FfiLengthResolutionContext, + pub out_data: *mut *const c_void, + pub display_before: FfiDisplay, + pub float_before: u16, + pub overflow_x_before: u16, + pub overflow_y_before: u16, + pub text_align_before: u16, + pub position_before: u16, + pub check_input_line_height: bool, + pub out_input_line_height_metrics: *mut FfiInputLineHeightMetrics, +} + +fn empty_longhand_batch_request() -> FfiLonghandBatchRequest { + FfiLonghandBatchRequest { + property_id: 0, + out_context: std::ptr::null_mut(), + out_data: std::ptr::null_mut(), + display_before: FfiDisplay::block(), + float_before: 0, + overflow_x_before: 0, + overflow_y_before: 0, + text_align_before: 0, + position_before: 0, + check_input_line_height: false, + out_input_line_height_metrics: std::ptr::null_mut(), + } +} /// The leaf callbacks the C++ side provides to the property computation /// driver. The driver selects each longhand's cascaded, inherited or initial @@ -1857,52 +2008,22 @@ pub const COMPUTED_KIND_DISPLAY: u8 = 9; #[repr(C)] pub struct FfiLonghandCallbacks { pub context: *mut c_void, - /// Stores a batch of selected values, applying each entry's side effects - /// and any remaining C++ computation in property order. The driver - /// flushes the batch before any callback that may read the stored - /// values, so the C++ side always observes the same compute and store - /// sequence as one call per property would produce. Every entry's shell - /// stays alive for the duration of the drive: cascaded values are - /// retained by the store, initial values are immortal, and parent values - /// are pinned by the snapshot or the fetch below. - pub store_computed_batch: - unsafe extern "C" fn(context: *mut c_void, entries: *const FfiComputedStoreEntry, count: usize), - /// Stores the used color scheme resolved from the computed color-scheme - /// value and document preferences. - pub store_effective_color_scheme: unsafe extern "C" fn(context: *mut c_void, color_scheme: u8), - /// Stores the original adjusted properties for possible animation - /// restoration, records the pre-transformation display, and returns the - /// font measurements needed for the input line-height decision. - pub prepare_post_compute_adjustments: unsafe extern "C" fn( + /// Applies one ordered action batch. It first stores the selected values, + /// including each entry's side effects and any remaining C++ computation, + /// then stores a nonnegative effective color scheme and constructs a + /// requested length context. The ordering lets a returned context observe + /// everything computed before its request. Every entry's value stays alive + /// for the duration of the drive: cascaded data is retained by the store, + /// initial values are immortal, and parent shells are pinned by the + /// snapshot or the fetch below. + pub execute_computation_batch: unsafe extern "C" fn( context: *mut c_void, - display_before: *const FfiDisplay, - float_before: u16, - overflow_x_before: u16, - overflow_y_before: u16, - text_align_before: u16, - position_before: u16, - check_input_line_height: bool, - ) -> FfiInputLineHeightMetrics, - /// Rare: fetches the parent's computed value for an explicit `inherit` of - /// a non-inherited property, which the parent snapshot does not carry. - /// The C++ side pins the returned shell until the end of the drive, so - /// deferred store batches may hold it. - pub fetch_non_inherited_parent_value: - unsafe extern "C" fn(context: *mut c_void, inherited_property_id: u16) -> FfiShellAndData, - /// Maps a nested value's shell pointer to its Rust-owned data while the - /// driver decides inheritance dependence. - pub data_of: unsafe extern "C" fn(shell: *const c_void) -> *const c_void, - /// Decides computational independence for value kinds whose rule still - /// lives with their C++ shells. - pub computational_independence_fallback: unsafe extern "C" fn(shell: *const c_void) -> bool, - /// Returns the element's computed writing mode and direction, packed as - /// writing_mode | direction << 8. - pub writing_mode_and_direction: unsafe extern "C" fn(context: *mut c_void) -> u16, - /// Fetches the length resolution context the property's computation would - /// use; the driver caches one per context kind and flushes pending stores - /// first, since building a context reads stored values. - pub length_resolution_context: - unsafe extern "C" fn(context: *mut c_void, property_id: u16, out: *mut FfiLengthResolutionContext), + entries: *const FfiComputedStoreEntry, + count: usize, + effective_color_scheme: i16, + request_kind: u8, + request: *const FfiLonghandBatchRequest, + ), } /// Document-level inputs to used color-scheme resolution. Scheme values use @@ -2032,12 +2153,12 @@ fn property_has_dedicated_compute_rule(property_id: u16) -> bool { } /// The parent's inheritable computed values, prepared once per element: one -/// (shell, data) entry per inherited-by-default longhand in property id +/// shared Rust data identity per inherited-by-default longhand in property id /// order. Null entries mark values the parent could not provide. The C++ side -/// pins every entry for the duration of the drive. +/// pins every owning facade for the duration of the drive. #[repr(C)] pub struct FfiParentSnapshot { - pub entries: *const FfiShellAndData, + pub entries: *const *const c_void, pub entry_count: usize, pub font_metrics_depend_on_viewport_metrics: bool, } @@ -2050,9 +2171,9 @@ pub struct FfiLonghandDriverResults { pub important_words: *mut u64, pub inherited_words: *mut u64, pub word_count: usize, - /// The raw winning cascaded font-size value, or null; borrowed from the + /// The raw winning cascaded font-size value data, or null; borrowed from the /// cascaded property store. - pub raw_cascaded_font_size_shell: *const c_void, + pub raw_cascaded_font_size_data: *const c_void, pub depends_on_viewport_metrics: bool, pub font_metrics_depend_on_viewport_metrics: bool, pub explicitly_inherited_non_inherited_property: bool, @@ -2147,6 +2268,7 @@ pub unsafe extern "C" fn rust_drive_property_computation( // Store operations queued for properties that need no computation, flushed in one // crossing before any callback that may read the stored values. let mut pending_stores: Vec = Vec::new(); + let mut pending_effective_color_scheme: i16 = -1; // Length resolution contexts fetched from C++ on first use, one per kind, like // the C++ side's per-element computation context caches. let mut cached_length_resolution_contexts: [Option; 3] = [None; 3]; @@ -2154,15 +2276,26 @@ pub unsafe extern "C" fn rust_drive_property_computation( callbacks: &FfiLonghandCallbacks, context: *mut c_void, pending_stores: &mut Vec, + pending_effective_color_scheme: &mut i16, ) { - if pending_stores.is_empty() { + if pending_stores.is_empty() && *pending_effective_color_scheme < 0 { return; } crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandStoreBatchCallback); - // SAFETY: The entries and their shells stay alive for the call; the callback + // SAFETY: The entries and their values stay alive for the call; the callback // table outlives the drive. - unsafe { (callbacks.store_computed_batch)(context, pending_stores.as_ptr(), pending_stores.len()) }; + unsafe { + (callbacks.execute_computation_batch)( + context, + pending_stores.as_ptr(), + pending_stores.len(), + *pending_effective_color_scheme, + LONGHAND_BATCH_REQUEST_NONE, + std::ptr::null(), + ); + }; pending_stores.clear(); + *pending_effective_color_scheme = -1; } fn fetch_length_resolution_context<'a>( @@ -2170,17 +2303,31 @@ pub unsafe extern "C" fn rust_drive_property_computation( callbacks: &FfiLonghandCallbacks, context: *mut c_void, pending_stores: &mut Vec, + pending_effective_color_scheme: &mut i16, kind: usize, property_id: u16, ) -> &'a FfiLengthResolutionContext { if caches[kind].is_none() { - // Building a context on the C++ side reads stored values. - flush_pending_stores(callbacks, context, pending_stores); - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandContextFetchCallback); + // Building a context on the C++ side reads stored values, so request it + // as part of the same ordered action batch that applies those values. + crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandStoreBatchCallback); let mut fetched = std::mem::MaybeUninit::::uninit(); - // SAFETY: The callback fills the context before returning. + // SAFETY: The callback applies the entries in order and then fills the + // requested context before returning. caches[kind] = Some(unsafe { - (callbacks.length_resolution_context)(context, property_id, fetched.as_mut_ptr()); + let mut request = empty_longhand_batch_request(); + request.property_id = property_id; + request.out_context = fetched.as_mut_ptr(); + (callbacks.execute_computation_batch)( + context, + pending_stores.as_ptr(), + pending_stores.len(), + *pending_effective_color_scheme, + LONGHAND_BATCH_REQUEST_LENGTH_CONTEXT, + &raw const request, + ); + pending_stores.clear(); + *pending_effective_color_scheme = -1; fetched.assume_init() }); } @@ -2193,7 +2340,7 @@ pub unsafe extern "C" fn rust_drive_property_computation( let index = (property_id - FIRST_INHERITED_PROPERTY_ID) as usize; assert!(index < snapshot.entry_count); // SAFETY: Snapshot entries are valid for the drive. - unsafe { ((*snapshot.entries.add(index)).data as *const StyleValueData).as_ref() } + unsafe { ((*snapshot.entries.add(index)) as *const StyleValueData).as_ref() } } // The computed math-depth, remembered for the font-size rule; None when C++ @@ -2225,17 +2372,14 @@ pub unsafe extern "C" fn rust_drive_property_computation( // logical property group exactly when either mapping table maps it. let is_logical_alias = table_row_maps(&LOGICAL_ALIAS_TABLE, property_id); if is_logical_alias || table_row_maps(&PHYSICAL_TO_LOGICAL_TABLE, property_id) { - if cached_writing_mode_and_direction.is_none() { - if let (Some(writing_mode), Some(direction)) = (computed_writing_mode, computed_direction) { - cached_writing_mode_and_direction = Some((writing_mode, direction)); - } else { - flush_pending_stores(callbacks, context, &mut pending_stores); - } - } let (writing_mode, direction) = *cached_writing_mode_and_direction.get_or_insert_with(|| { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandWritingModeCallback); - let packed = unsafe { (callbacks.writing_mode_and_direction)(context) }; - ((packed & 0xff) as u8, (packed >> 8) as u8) + // Direction and writing-mode precede every logical property in the + // generated computation order and only accept keywords, so their + // selected values are their computed mapping inputs. + ( + computed_writing_mode.expect("writing-mode must precede logical properties"), + computed_direction.expect("direction must precede logical properties"), + ) }); let counterpart_property_id = if is_logical_alias { let physical = map_logical_alias_to_physical(property_id, writing_mode, direction); @@ -2251,19 +2395,22 @@ pub unsafe extern "C" fn rust_drive_property_computation( cascaded_property_id = store.property_with_higher_priority(property_id, counterpart_property_id); } - let mut value = FfiShellAndData::null(); - if let Some((value_shell, value_data, important)) = store.winning_declaration(cascaded_property_id) { - value = FfiShellAndData { - shell: value_shell, - data: value_data, - }; + let mut value = std::ptr::null(); + let mut source_slot = -1; + let mut has_style_sheet_context = false; + if let Some((value_data, important, cascaded_source_slot, cascaded_has_style_sheet_context)) = + store.winning_declaration(cascaded_property_id) + { + value = value_data; + source_slot = i64::from(cascaded_source_slot); + has_style_sheet_context = cascaded_has_style_sheet_context; if important { set_longhand_bit(important_words, property_id); } // Keep the raw winning cascaded font-size for the monospace font-size // recascade (see recascade_font_size_if_needed on the C++ side). if property_id == crate::property_metadata::property_id::FONT_SIZE { - results.raw_cascaded_font_size_shell = value_shell; + results.raw_cascaded_font_size_data = value_data; } } else if property_id == crate::property_metadata::property_id::FONT_SIZE && has_new_font_size { // NOTE: The recascaded font-size has already been stored before the loop. @@ -2271,10 +2418,10 @@ pub unsafe extern "C" fn rust_drive_property_computation( } let decision = longhand_decision( - if value.data.is_null() { + if value.is_null() { None } else { - Some(unsafe { &*(value.data as *const StyleValueData) }) + Some(unsafe { &*(value as *const StyleValueData) }) }, property_id, ); @@ -2285,6 +2432,8 @@ pub unsafe extern "C" fn rust_drive_property_computation( let inherit_fetch_attempted = decision.should_inherit && has_inheritance_parent; if inherit_fetch_attempted { + source_slot = -1; + has_style_sheet_context = false; let snapshot = snapshot.unwrap(); set_longhand_bit(inherited_words, property_id); if decision.explicitly_inherits_non_inherited_property { @@ -2296,7 +2445,21 @@ pub unsafe extern "C" fn rust_drive_property_computation( unsafe { *snapshot.entries.add(index) } } else { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandParentValueFetchCallback); - unsafe { (callbacks.fetch_non_inherited_parent_value)(context, inherited_property_id) } + let mut parent_data = std::ptr::null(); + let mut request = empty_longhand_batch_request(); + request.property_id = inherited_property_id; + request.out_data = &raw mut parent_data; + unsafe { + (callbacks.execute_computation_batch)( + context, + std::ptr::null(), + 0, + -1, + LONGHAND_BATCH_REQUEST_PARENT_VALUE, + &raw const request, + ); + }; + parent_data }; if property_affects_font_metrics(inherited_property_id) && snapshot.font_metrics_depend_on_viewport_metrics @@ -2307,12 +2470,14 @@ pub unsafe extern "C" fn rust_drive_property_computation( } let use_initial = if inherit_fetch_attempted { - value.data.is_null() || value_is_initial_or_unset(value.data) + value.is_null() || value_is_initial_or_unset(value) } else { decision.use_initial_without_inherit }; if use_initial { - value = initial_value(property_id); + source_slot = -1; + has_style_sheet_context = false; + value = initial_value_data(property_id).cast(); required_level = REQUIRES_COMPUTATION_NON_INHERITED; } @@ -2320,7 +2485,7 @@ pub unsafe extern "C" fn rust_drive_property_computation( // Whether the computed value depends on inherited information, so the specified // value must be kept for re-resolution when an ancestor changes. - let value_data = unsafe { &*(value.data as *const StyleValueData) }; + let value_data = unsafe { &*(value as *const StyleValueData) }; if inherited_property_id == crate::property_metadata::property_id::BACKGROUND_IMAGE && let StyleValueData::ValueList { values, .. } = value_data @@ -2334,18 +2499,10 @@ pub unsafe extern "C" fn rust_drive_property_computation( computed_direction = keyword_to_direction(*keyword); } } - let inheritance_dependent = - crate::style_value::value_depends_on_current_color(value_data, callbacks.data_of) - || !value_is_computationally_independent( - value_data, - callbacks.data_of, - callbacks.computational_independence_fallback, - ) - .unwrap_or_else(|| { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandIndependenceFallbackCallback); - unsafe { (callbacks.computational_independence_fallback)(value.shell) } - }) - || value_depends_on_inherited_info_for_property(value_data, property_id); + let inheritance_dependent = crate::style_value::value_depends_on_current_color(value_data) + || !value_is_computationally_independent(value_data) + .expect("computational independence requested for an unsupported value") + || value_depends_on_inherited_info_for_property(value_data, property_id); if requires_computation { // Plain length values of properties without a dedicated computed-value rule @@ -2366,6 +2523,7 @@ pub unsafe extern "C" fn rust_drive_property_computation( callbacks, context, &mut pending_stores, + &mut pending_effective_color_scheme, kind, inherited_property_id, ); @@ -2397,6 +2555,7 @@ pub unsafe extern "C" fn rust_drive_property_computation( Number(f64), Percentage(f64), FontStyle(u8), + StyleValue(Arc), } use crate::property_metadata::property_id as prop; let synthesized_px_length = |absolutized: Option| { @@ -2577,6 +2736,7 @@ pub unsafe extern "C" fn rust_drive_property_computation( callbacks, context, &mut pending_stores, + &mut pending_effective_color_scheme, ComputationContextKind::LineHeight as usize, prop::LINE_HEIGHT, ); @@ -2651,6 +2811,10 @@ pub unsafe extern "C" fn rust_drive_property_computation( NativeValue::Unsupported } } + (_, prop::POSITION_AREA) => match compute_position_area(value_data) { + Some(value) => NativeValue::StyleValue(value), + None => NativeValue::Unchanged, + }, (Some(absolutized), _) if !property_has_dedicated_compute_rule(inherited_property_id) => { match absolutized { Some(px) => NativeValue::Px(px), @@ -2660,25 +2824,31 @@ pub unsafe extern "C" fn rust_drive_property_computation( _ => NativeValue::Unsupported, }; - let (computed_kind, computed_value) = match native { - NativeValue::Px(px) => (COMPUTED_KIND_PX_LENGTH, px), - NativeValue::Integer(integer) => (COMPUTED_KIND_INTEGER, integer as f64), - NativeValue::Superellipse(parameter) => (COMPUTED_KIND_SUPERELLIPSE, parameter), - NativeValue::Number(number) => (COMPUTED_KIND_NUMBER, number), - NativeValue::Percentage(percentage) => (COMPUTED_KIND_PERCENTAGE, percentage), - NativeValue::FontStyle(font_style_keyword) => (COMPUTED_KIND_FONT_STYLE, font_style_keyword as f64), - NativeValue::Unchanged => (COMPUTED_KIND_SHELL, 0.0), + let (computed_kind, computed_value, computed_data) = match native { + NativeValue::Px(px) => (COMPUTED_KIND_PX_LENGTH, px, std::ptr::null()), + NativeValue::Integer(integer) => (COMPUTED_KIND_INTEGER, integer as f64, std::ptr::null()), + NativeValue::Superellipse(parameter) => (COMPUTED_KIND_SUPERELLIPSE, parameter, std::ptr::null()), + NativeValue::Number(number) => (COMPUTED_KIND_NUMBER, number, std::ptr::null()), + NativeValue::Percentage(percentage) => (COMPUTED_KIND_PERCENTAGE, percentage, std::ptr::null()), + NativeValue::FontStyle(font_style_keyword) => { + (COMPUTED_KIND_FONT_STYLE, font_style_keyword as f64, std::ptr::null()) + } + NativeValue::StyleValue(value) => (COMPUTED_KIND_STYLE_VALUE, 0.0, Arc::into_raw(value).cast()), + NativeValue::Unchanged => (COMPUTED_KIND_UNCHANGED, 0.0, std::ptr::null()), NativeValue::Unsupported => { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandCppComputeFallback); - (COMPUTED_KIND_COMPUTE_IN_CPP, 0.0) + (COMPUTED_KIND_COMPUTE_IN_CPP, 0.0, std::ptr::null()) } }; pending_stores.push(FfiComputedStoreEntry { property_id, inherited_property_id, - shell: value.shell, + data: value, + source_slot, + has_style_sheet_context, inheritance_dependent, inherited: inherit_fetch_attempted, + computed_data, computed_kind, value: computed_value, }); @@ -2686,10 +2856,13 @@ pub unsafe extern "C" fn rust_drive_property_computation( pending_stores.push(FfiComputedStoreEntry { property_id, inherited_property_id, - shell: value.shell, + data: value, + source_slot, + has_style_sheet_context, inheritance_dependent, inherited: inherit_fetch_attempted, - computed_kind: COMPUTED_KIND_SHELL, + computed_data: std::ptr::null(), + computed_kind: COMPUTED_KIND_UNCHANGED, value: 0.0, }); } @@ -2706,13 +2879,13 @@ pub unsafe extern "C" fn rust_drive_property_computation( let effective_overflow = resolve_effective_overflow_keywords(overflow_x, *overflow_y); for entry in pending_stores.iter_mut().rev() { if entry.property_id == prop::OVERFLOW_X && effective_overflow.changed_x { - debug_assert_eq!(entry.computed_kind, COMPUTED_KIND_SHELL); + debug_assert_eq!(entry.computed_kind, COMPUTED_KIND_UNCHANGED); entry.computed_kind = COMPUTED_KIND_KEYWORD; entry.value = effective_overflow.x_keyword as f64; clear_longhand_bit(important_words, prop::OVERFLOW_X); clear_longhand_bit(inherited_words, prop::OVERFLOW_X); } else if entry.property_id == prop::OVERFLOW_Y && effective_overflow.changed_y { - debug_assert_eq!(entry.computed_kind, COMPUTED_KIND_SHELL); + debug_assert_eq!(entry.computed_kind, COMPUTED_KIND_UNCHANGED); entry.computed_kind = COMPUTED_KIND_KEYWORD; entry.value = effective_overflow.y_keyword as f64; clear_longhand_bit(important_words, prop::OVERFLOW_Y); @@ -2752,7 +2925,7 @@ pub unsafe extern "C" fn rust_drive_property_computation( if adjustment.changed { let entry = pending_stores.last_mut().unwrap(); debug_assert_eq!(entry.property_id, prop::TEXT_ALIGN); - debug_assert_eq!(entry.computed_kind, COMPUTED_KIND_SHELL); + debug_assert_eq!(entry.computed_kind, COMPUTED_KIND_UNCHANGED); entry.computed_kind = COMPUTED_KIND_KEYWORD; entry.value = adjustment.keyword as f64; clear_longhand_bit(important_words, property_id); @@ -2774,7 +2947,7 @@ pub unsafe extern "C" fn rust_drive_property_computation( color_scheme_input.preferred_color_scheme, unsafe { color_scheme_input.document_supported_schemes() }, ); - unsafe { (callbacks.store_effective_color_scheme)(context, color_scheme) }; + pending_effective_color_scheme = i16::from(color_scheme); } match (property_id, value_data) { @@ -2791,7 +2964,12 @@ pub unsafe extern "C" fn rust_drive_property_computation( } } - flush_pending_stores(callbacks, context, &mut pending_stores); + flush_pending_stores( + callbacks, + context, + &mut pending_stores, + &mut pending_effective_color_scheme, + ); let display_before = computed_display.expect("display must be computed by the longhand driver"); let mut box_type_input = unsafe { *box_type_input }; box_type_input.display = display_before; @@ -2827,18 +3005,29 @@ pub unsafe extern "C" fn rust_drive_property_computation( clear_longhand_bit(important_words, prop::TEXT_ALIGN); clear_longhand_bit(inherited_words, prop::TEXT_ALIGN); } - let input_line_height_metrics = unsafe { - (callbacks.prepare_post_compute_adjustments)( + crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandStoreBatchCallback); + let mut input_line_height_metrics = std::mem::MaybeUninit::::uninit(); + let mut request = empty_longhand_batch_request(); + request.display_before = display_before; + request.float_before = float_before; + request.overflow_x_before = computed_overflow_x.expect("overflow-x must be computed by the longhand driver"); + request.overflow_y_before = computed_overflow_y.expect("overflow-y must be computed by the longhand driver"); + request.text_align_before = + computed_text_align_before_adjustment.expect("text-align must be computed by the longhand driver"); + request.position_before = box_type_input.position; + request.check_input_line_height = element_adjustment.check_input_line_height; + request.out_input_line_height_metrics = input_line_height_metrics.as_mut_ptr(); + unsafe { + (callbacks.execute_computation_batch)( context, - &raw const display_before, - float_before, - computed_overflow_x.expect("overflow-x must be computed by the longhand driver"), - computed_overflow_y.expect("overflow-y must be computed by the longhand driver"), - computed_text_align_before_adjustment.expect("text-align must be computed by the longhand driver"), - box_type_input.position, - element_adjustment.check_input_line_height, - ) - }; + std::ptr::null(), + 0, + -1, + LONGHAND_BATCH_REQUEST_POST_COMPUTE_ADJUSTMENTS, + &raw const request, + ); + } + let input_line_height_metrics = unsafe { input_line_height_metrics.assume_init() }; let clamp_input_line_height = should_clamp_input_line_height(&element_adjustment, &input_line_height_metrics); let adjusted_display = if element_adjustment.changed_display { @@ -2849,9 +3038,12 @@ pub unsafe extern "C" fn rust_drive_property_computation( let adjusted_entry = |property_id, computed_kind, value| FfiComputedStoreEntry { property_id, inherited_property_id: property_id, - shell: initial_value(property_id).shell, + data: initial_value_data(property_id).cast(), + source_slot: -1, + has_style_sheet_context: false, inheritance_dependent: false, inherited: false, + computed_data: std::ptr::null(), computed_kind, value, }; @@ -2890,7 +3082,16 @@ pub unsafe extern "C" fn rust_drive_property_computation( } if !adjustments.is_empty() { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::LonghandStoreBatchCallback); - unsafe { (callbacks.store_computed_batch)(context, adjustments.as_ptr(), adjustments.len()) }; + unsafe { + (callbacks.execute_computation_batch)( + context, + adjustments.as_ptr(), + adjustments.len(), + -1, + LONGHAND_BATCH_REQUEST_NONE, + std::ptr::null(), + ); + }; } if line_height_changed { clear_longhand_bit(important_words, prop::LINE_HEIGHT); @@ -2904,18 +3105,12 @@ fn property_affects_font_metrics(property_id: u16) -> bool { || property_id == crate::property_metadata::property_id::LINE_HEIGHT } -/// Shell-level callbacks for the shorthand expansion recursion. Values cross -/// as opaque C++ style value shells; the C++ side pins every value it creates -/// until the expansion returns. +/// Callback for each longhand produced by shorthand expansion. Values are +/// borrowed shared Rust data handles valid for the duration of the call. #[repr(C)] pub struct FfiShorthandExpansionCallbacks { pub context: *mut c_void, - /// Returns the Rust-owned data of a C++ style value shell. - pub data_of: unsafe extern "C" fn(context: *mut c_void, shell: *const c_void) -> *const c_void, - /// Creates and pins a pending-substitution value wrapping the given value; - /// returns its shell. - pub create_pending_substitution: unsafe extern "C" fn(context: *mut c_void, shell: *const c_void) -> *const c_void, - pub set_longhand_property: unsafe extern "C" fn(context: *mut c_void, property_id: u16, shell: *const c_void), + pub set_longhand_property: unsafe extern "C" fn(context: *mut c_void, property_id: u16, data: *const c_void), } pub(crate) fn value_is_css_wide_keyword(value: &StyleValueData) -> bool { @@ -2928,20 +3123,16 @@ pub(crate) fn value_is_css_wide_keyword(value: &StyleValueData) -> bool { } } -/// The expansion recursion over `(shell, data)` value pairs. `data_of` returns the Rust-owned -/// data of a shell, `create_pending_substitution` wraps a shell in a pinned -/// pending-substitution value, and `sink` receives each `(longhand id, shell, data)` result. -pub(crate) fn expand_shorthands_with( - data_of: &DataOf, - create_pending_substitution: &CreatePendingSubstitution, +/// The expansion recursion over shared Rust value data. `has_style_sheet_context` +/// follows boundary values through CSS-wide propagation, while nested shorthand +/// values have no facade-local resource context. +pub(crate) fn expand_shorthands_with( property_id: u16, - shell: *const c_void, data: *const c_void, + has_style_sheet_context: bool, sink: &mut Sink, ) where - DataOf: Fn(*const c_void) -> *const c_void, - CreatePendingSubstitution: Fn(*const c_void) -> *const c_void, - Sink: FnMut(u16, *const c_void, *const c_void), + Sink: FnMut(u16, *const c_void, bool), { let value = unsafe { &*(data as *const StyleValueData) }; let is_shorthand = property_is_shorthand(property_id); @@ -2959,18 +3150,12 @@ pub(crate) fn expand_shorthands_with( // determined until after substituted. // https://drafts.csswg.org/css-values-5/#pending-substitution-value // Ensure we keep the longhand around until it can be resolved. - sink(property_id, shell, data); - let pending = create_pending_substitution(shell); - let pending_data = data_of(pending); + sink(property_id, data, has_style_sheet_context); + let retained_data = unsafe { crate::style_value::rust_style_value_retain(data.cast::()) }; + let pending_data = unsafe { crate::style_value::rust_style_value_create_pending_substitution(retained_data) }; + let pending = unsafe { RetainedStyleValueData::from_retained_pointer(pending_data) }; for &longhand in longhands_for_shorthand(property_id) { - expand_shorthands_with( - data_of, - create_pending_substitution, - longhand, - pending, - pending_data, - sink, - ); + expand_shorthands_with(longhand, pending.pointer().cast(), has_style_sheet_context, sink); } return; } @@ -2980,16 +3165,8 @@ pub(crate) fn expand_shorthands_with( } = value { for (&sub_property, sub_value) in sub_properties.as_slice().iter().zip(values.as_slice()) { - let sub_shell = sub_value.shell_pointer(); - let sub_data = data_of(sub_shell); - expand_shorthands_with( - data_of, - create_pending_substitution, - sub_property, - sub_shell, - sub_data, - sink, - ); + let sub_data = sub_value.pointer().cast(); + expand_shorthands_with(sub_property, sub_data, false, sink); } return; } @@ -3001,55 +3178,40 @@ pub(crate) fn expand_shorthands_with( // because the longhands might have longhands of their own. assert!(value_is_css_wide_keyword(value) || matches!(value, StyleValueData::GuaranteedInvalid)); for &longhand in longhands_for_shorthand(property_id) { - expand_shorthands_with(data_of, create_pending_substitution, longhand, shell, data, sink); + expand_shorthands_with(longhand, data, has_style_sheet_context, sink); } return; } - sink(property_id, shell, data); + sink(property_id, data, has_style_sheet_context); } -fn expand_shorthands( - callbacks: &FfiShorthandExpansionCallbacks, - property_id: u16, - shell: *const c_void, - data: *const c_void, -) { +fn expand_shorthands(callbacks: &FfiShorthandExpansionCallbacks, property_id: u16, data: *const c_void) { let context = callbacks.context; - expand_shorthands_with( - &|shell| { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadeDataOfCallback); - unsafe { (callbacks.data_of)(context, shell) } - }, - &|shell| { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::CascadePendingSubstitutionCallback); - unsafe { (callbacks.create_pending_substitution)(context, shell) } - }, - property_id, - shell, - data, - &mut |longhand_id, longhand_shell, _longhand_data| { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::ShorthandSetLonghandCallback); - unsafe { (callbacks.set_longhand_property)(context, longhand_id, longhand_shell) }; - }, - ); + expand_shorthands_with(property_id, data, false, &mut |longhand_id, longhand_data, _| { + crate::ffi_stats::bump(crate::ffi_stats::FfiOp::ShorthandSetLonghandCallback); + unsafe { (callbacks.set_longhand_property)(context, longhand_id, longhand_data) }; + }); } /// Expands a declared property into longhand assignments, recursing through /// shorthand and pending-substitution values. /// /// # Safety -/// `callbacks` must be a valid callback table and `shell`/`data` a valid -/// C++ style value and its Rust-owned data. +/// `callbacks` must be a valid callback table and `data` valid Rust-owned +/// style value data. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_for_each_property_expanding_shorthands( callbacks: *const FfiShorthandExpansionCallbacks, property_id: u16, - shell: *const c_void, data: *const c_void, ) { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::ShorthandExpansionEntry); - abort_on_panic(|| expand_shorthands(unsafe { &*callbacks }, property_id, shell, data)); + abort_on_panic(|| expand_shorthands(unsafe { &*callbacks }, property_id, data)); +} + +pub(crate) fn display_is_none(raw: u32) -> bool { + FfiDisplay::from_raw(raw).is_none() } /// The element facts the box type transformation needs, marshalled by the C++ @@ -3526,12 +3688,6 @@ pub extern "C" fn rust_style_compute_context_anchor(_context: *const c_void) {} // that StyleValueData's retained members call on drop are stubbed out here. #[cfg(test)] mod ffi_test_stubs { - use std::ffi::c_void; - - #[unsafe(no_mangle)] - extern "C" fn ladybird_style_value_unref(_style_value: *const c_void) {} - #[unsafe(no_mangle)] - extern "C" fn ladybird_style_value_ref(_style_value: *const c_void) {} #[unsafe(no_mangle)] extern "C" fn ladybird_utf16_fly_string_unref(_raw: usize) {} #[unsafe(no_mangle)] diff --git a/Libraries/LibWeb/CSS/Rust/src/style_value.rs b/Libraries/LibWeb/CSS/Rust/src/style_value.rs index 20e2dd4b0111e..8e77e0981fc7e 100644 --- a/Libraries/LibWeb/CSS/Rust/src/style_value.rs +++ b/Libraries/LibWeb/CSS/Rust/src/style_value.rs @@ -4,79 +4,139 @@ * SPDX-License-Identifier: BSD-2-Clause */ +// Style value identities are shared across the FFI boundary, but remain confined to the thread +// owning the C++ style objects they may currently retain. +#![allow(clippy::arc_with_non_send_sync)] + //! Rust-owned CSS style value data. //! -//! The C++ StyleValue subclasses keep their data in a Rust-owned [`StyleValueData`] allocation -//! instead of C++ member variables. Each subclass owns its allocation uniquely and destroys it -//! with [`rust_style_value_destroy`]. The layout of [`StyleValueData`] is exposed to C++ +//! The C++ StyleValue subclasses keep their data in a Rust-owned, reference-counted +//! [`StyleValueData`] allocation instead of C++ member variables. The layout of +//! [`StyleValueData`] is exposed to C++ //! through cbindgen so that hot accessors compile to inline field reads with no FFI call. use std::ffi::c_void; +use std::sync::Arc; use crate::abort_on_panic; unsafe extern "C" { - fn ladybird_style_value_unref(style_value: *const c_void); fn ladybird_utf16_fly_string_unref(raw: usize); fn ladybird_string_unref(raw: usize); - fn ladybird_style_value_ref(style_value: *const c_void); fn ladybird_utf16_fly_string_ref(raw: usize); } -/// A strong reference to a C++ StyleValue held from Rust-owned value data. -/// -/// Nested values still point at the C++ shell objects; dropping the Rust allocation releases -/// the reference. Once the shells are collapsed into a single handle type these become -/// references between Rust allocations instead. +/// A strong reference to immutable Rust-owned style value data. #[repr(C)] -pub struct RetainedStyleValue { +pub struct RetainedStyleValueData { pointer: *const c_void, } -impl RetainedStyleValue { - pub(crate) fn shell_pointer(&self) -> *const c_void { - self.pointer +impl PartialEq for RetainedStyleValueData { + fn eq(&self, other: &Self) -> bool { + match (self.optional_data(), other.optional_data()) { + (Some(first), Some(second)) => std::ptr::eq(first, second) || first == second, + (None, None) => true, + _ => false, + } + } +} + +impl RetainedStyleValueData { + pub(crate) fn pointer(&self) -> *const StyleValueData { + self.pointer.cast() } - /// Assumes ownership of one strong reference to the C++ StyleValue shell. + pub(crate) fn data(&self) -> &StyleValueData { + unsafe { &*self.pointer.cast::() } + } + + pub(crate) fn optional_data(&self) -> Option<&StyleValueData> { + unsafe { self.pointer.cast::().as_ref() } + } + + /// Assumes ownership of one strong reference to Rust-owned style value data. /// /// # Safety - /// `pointer` must be a leaked strong StyleValue reference (or null for an absent value). - pub(crate) unsafe fn from_shell_pointer(pointer: *const c_void) -> Self { - Self { pointer } + /// `pointer` must be a strong reference returned by `rust_style_value_retain`. + pub(crate) unsafe fn from_retained_pointer(pointer: *const StyleValueData) -> Self { + debug_assert!(!pointer.is_null()); + Self { + pointer: pointer.cast(), + } } - /// Retains a new strong reference to the C++ StyleValue shell. + /// Assumes ownership of one strong reference when `pointer` is non-null. /// /// # Safety - /// `pointer` must point at a live StyleValue shell. - pub(crate) unsafe fn from_borrowed_shell_pointer(pointer: *const c_void) -> Self { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StyleValueShellRetainCallback); - unsafe { ladybird_style_value_ref(pointer) }; - Self { pointer } + /// `pointer` must be null or a strong reference returned by `rust_style_value_retain`. + pub(crate) unsafe fn from_retained_optional_pointer(pointer: *const StyleValueData) -> Self { + Self { + pointer: pointer.cast(), + } } - /// Clones the retained reference, bumping the shell's reference count. pub(crate) fn clone_retained(&self) -> Self { - unsafe { Self::from_borrowed_shell_pointer(self.pointer) } + let pointer = unsafe { rust_style_value_retain(self.pointer.cast()) }; + unsafe { Self::from_retained_optional_pointer(pointer) } } } -/// Retains one strong reference to a C++ StyleValue shell for a reference -/// slot poked into a style group payload. -pub(crate) fn retain_shell_pointer(pointer: *const c_void) { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StyleValueShellRetainCallback); - // SAFETY: The caller guarantees a live shell. - unsafe { ladybird_style_value_ref(pointer) }; +impl Drop for RetainedStyleValueData { + fn drop(&mut self) { + unsafe { rust_style_value_release(self.pointer.cast()) }; + } } -impl Drop for RetainedStyleValue { - fn drop(&mut self) { - // A null pointer represents an absent optional reference. - if !self.pointer.is_null() { - crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StyleValueShellReleaseCallback); - unsafe { ladybird_style_value_unref(self.pointer) }; +/// A retained, Rust-owned array of shared style value data references. +#[repr(C)] +pub struct RetainedStyleValueDataList { + pointer: *mut RetainedStyleValueData, + length: usize, +} + +impl RetainedStyleValueDataList { + pub(crate) fn as_slice(&self) -> &[RetainedStyleValueData] { + if self.pointer.is_null() { + return &[]; } + unsafe { std::slice::from_raw_parts(self.pointer, self.length) } + } + + pub(crate) fn from_retained_values(values: Vec) -> Self { + let slice = values.into_boxed_slice(); + let length = slice.len(); + let pointer = Box::into_raw(slice) as *mut RetainedStyleValueData; + Self { pointer, length } + } + + /// Takes ownership of one strong reference to each value. + /// + /// # Safety + /// `values` must point to `length` strong references returned by `rust_style_value_retain`. + unsafe fn from_retained_pointers(values: *const *const StyleValueData, length: usize) -> Self { + let slice: Box<[RetainedStyleValueData]> = (0..length) + .map(|i| unsafe { RetainedStyleValueData::from_retained_pointer(*values.add(i)) }) + .collect(); + let length = slice.len(); + let pointer = Box::into_raw(slice) as *mut RetainedStyleValueData; + Self { pointer, length } + } + + /// Takes ownership of one strong reference to each non-null value. + /// + /// # Safety + /// Every non-null entry in `values` must be a strong reference returned by + /// `rust_style_value_retain`. + unsafe fn from_retained_optional_pointers(values: *const *const StyleValueData, length: usize) -> Self { + let slice: Box<[RetainedStyleValueData]> = (0..length) + .map(|i| RetainedStyleValueData { + pointer: unsafe { *values.add(i) }.cast(), + }) + .collect(); + let length = slice.len(); + let pointer = Box::into_raw(slice) as *mut RetainedStyleValueData; + Self { pointer, length } } } @@ -84,6 +144,7 @@ impl Drop for RetainedStyleValue { /// to the underlying string data unless it is a short string, which needs none; the C++ bridge /// handles both cases. #[repr(C)] +#[derive(PartialEq)] pub struct RetainedUtf16FlyString { raw: usize, } @@ -132,6 +193,8 @@ macro_rules! retained_list_drop { }; } +retained_list_drop!(RetainedStyleValueDataList); + /// Implements the shared behavior for a `Retained*List` struct whose `from_raw` input is an /// array of its own element type: `from_raw` copies `length` elements into a Rust-owned boxed /// slice, assuming ownership of the elements' retained references, and `Drop` releases them. @@ -156,39 +219,6 @@ macro_rules! retained_list { }; } -/// A retained, Rust-owned array of style value references. -#[repr(C)] -pub struct RetainedStyleValueList { - pointer: *mut RetainedStyleValue, - length: usize, -} - -impl RetainedStyleValueList { - pub(crate) fn as_slice(&self) -> &[RetainedStyleValue] { - if self.pointer.is_null() { - return &[]; - } - unsafe { std::slice::from_raw_parts(self.pointer, self.length) } - } - - /// Takes ownership of one strong reference to each value. - /// - /// # Safety - /// `values` must point to `length` valid style value pointers. - unsafe fn from_raw(values: *const *const c_void, length: usize) -> Self { - let slice: Box<[RetainedStyleValue]> = (0..length) - .map(|i| RetainedStyleValue { - pointer: unsafe { *values.add(i) }, - }) - .collect(); - let length = slice.len(); - let pointer = Box::into_raw(slice) as *mut RetainedStyleValue; - Self { pointer, length } - } -} - -retained_list_drop!(RetainedStyleValueList); - /// A Rust-owned array of C++ PropertyID values (`enum class PropertyID : u16`, opaque to Rust). #[repr(C)] pub struct RetainedPropertyIdList { @@ -212,12 +242,47 @@ impl RetainedPropertyIdList { #[repr(C)] pub struct RetainedString { raw: usize, + bytes: *mut u8, + length: usize, +} + +impl RetainedString { + /// Takes ownership of a leaked AK::String reference and copies its bytes. + /// + /// # Safety + /// `bytes` must point at `length` readable bytes. + unsafe fn from_raw(raw: usize, bytes: *const u8, length: usize) -> Self { + let readable = unsafe { RetainedReadableString::from_raw(raw, bytes, length) }; + let result = Self { + raw: readable.raw, + bytes: readable.bytes, + length: readable.length, + }; + std::mem::forget(readable); + result + } + + fn as_bytes(&self) -> &[u8] { + if self.bytes.is_null() { + return &[]; + } + unsafe { std::slice::from_raw_parts(self.bytes, self.length) } + } +} + +impl PartialEq for RetainedString { + fn eq(&self, other: &Self) -> bool { + self.as_bytes() == other.as_bytes() + } } impl Drop for RetainedString { fn drop(&mut self) { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StringRetainReleaseCallback); unsafe { ladybird_string_unref(self.raw) }; + if !self.bytes.is_null() { + drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(self.bytes, self.length)) }); + } } } @@ -258,6 +323,12 @@ impl RetainedReadableString { } } +impl PartialEq for RetainedReadableString { + fn eq(&self, other: &Self) -> bool { + self.as_bytes() == other.as_bytes() + } +} + impl Drop for RetainedReadableString { fn drop(&mut self) { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StringRetainReleaseCallback); @@ -272,6 +343,7 @@ impl Drop for RetainedReadableString { /// string value (raw 0 when the value is an enum). All enums are C++ `enum class ... : u8` /// values, opaque to Rust. #[repr(C)] +#[derive(PartialEq)] pub struct RetainedRequestUrlModifier { modifier_type: u8, enum_value: u8, @@ -304,6 +376,13 @@ pub struct RetainedUtf16FlyStringList { } impl RetainedUtf16FlyStringList { + pub(crate) fn from_retained_strings(strings: Vec) -> Self { + let slice = strings.into_boxed_slice(); + let length = slice.len(); + let pointer = Box::into_raw(slice) as *mut RetainedUtf16FlyString; + Self { pointer, length } + } + /// Takes ownership of one leaked reference to each string. /// /// # Safety @@ -318,6 +397,22 @@ impl RetainedUtf16FlyStringList { let pointer = Box::into_raw(slice) as *mut RetainedUtf16FlyString; Self { pointer, length } } + + pub(crate) fn clone_retained(&self) -> Self { + Self::from_retained_strings( + self.as_slice() + .iter() + .map(|string| unsafe { RetainedUtf16FlyString::from_borrowed_raw(string.raw()) }) + .collect(), + ) + } + + pub(crate) fn as_slice(&self) -> &[RetainedUtf16FlyString] { + if self.pointer.is_null() { + return &[]; + } + unsafe { std::slice::from_raw_parts(self.pointer, self.length) } + } } retained_list_drop!(RetainedUtf16FlyStringList); @@ -334,10 +429,11 @@ impl RetainedByteList { /// A retained counter definition: the counter name, the reversed flag and an optional retained /// value (null when absent). #[repr(C)] +#[derive(PartialEq)] pub struct RetainedCounterDefinition { name: RetainedUtf16FlyString, is_reversed: bool, - value: RetainedStyleValue, + value: RetainedStyleValueData, } /// A Rust-owned array of retained counter definitions. @@ -353,9 +449,10 @@ retained_list!(RetainedCounterDefinitionList, RetainedCounterDefinition); /// retained AK::Utf16String raw, 0 when absent, released through the same bridge as fly /// strings). #[repr(C)] +#[derive(PartialEq)] pub struct RetainedImageSetOption { - image: RetainedStyleValue, - resolution: RetainedStyleValue, + image: RetainedStyleValueData, + resolution: RetainedStyleValueData, has_type: bool, type_string: RetainedUtf16FlyString, } @@ -370,14 +467,14 @@ pub struct RetainedImageSetOptionList { retained_list!(RetainedImageSetOptionList, RetainedImageSetOption); /// A retained gradient color stop: an optional transition hint, then an optional color, -/// position and second position (each null when absent). The layout matches the C++ -/// ColorStopListElement, which is four reference pointers, so C++ views these in place. +/// position and second position (each null when absent). #[repr(C)] +#[derive(PartialEq)] pub struct RetainedColorStop { - transition_hint: RetainedStyleValue, - color: RetainedStyleValue, - position: RetainedStyleValue, - second_position: RetainedStyleValue, + transition_hint: RetainedStyleValueData, + color: RetainedStyleValueData, + position: RetainedStyleValueData, + second_position: RetainedStyleValueData, } /// A Rust-owned array of retained gradient color stops. @@ -390,19 +487,19 @@ pub struct RetainedColorStopList { retained_list!(RetainedColorStopList, RetainedColorStop); impl RetainedCounterDefinition { - pub(crate) fn value(&self) -> &RetainedStyleValue { + pub(crate) fn value(&self) -> &RetainedStyleValueData { &self.value } } impl RetainedImageSetOption { - pub(crate) fn values(&self) -> [&RetainedStyleValue; 2] { + pub(crate) fn values(&self) -> [&RetainedStyleValueData; 2] { [&self.image, &self.resolution] } } impl RetainedLinearEasingStop { - pub(crate) fn values(&self) -> [&RetainedStyleValue; 2] { + pub(crate) fn values(&self) -> [&RetainedStyleValueData; 2] { [&self.output, &self.input] } } @@ -424,15 +521,28 @@ retained_list_as_slice!(RetainedImageSetOptionList, RetainedImageSetOption); retained_list_as_slice!(RetainedLinearEasingStopList, RetainedLinearEasingStop); impl RetainedShapePoint { - pub(crate) fn values(&self) -> [&RetainedStyleValue; 2] { + pub(crate) fn values(&self) -> [&RetainedStyleValueData; 2] { [&self.x, &self.y] } + + pub(crate) fn from_retained_values(x: RetainedStyleValueData, y: RetainedStyleValueData) -> Self { + Self { x, y } + } } retained_list_as_slice!(RetainedShapePointList, RetainedShapePoint); +impl RetainedShapePointList { + pub(crate) fn from_retained_points(points: Vec) -> Self { + let slice = points.into_boxed_slice(); + let length = slice.len(); + let pointer = Box::into_raw(slice) as *mut RetainedShapePoint; + Self { pointer, length } + } +} + impl RetainedColorStop { /// The stop's retained values, absent ones as null retained references. - pub(crate) fn values(&self) -> [&RetainedStyleValue; 4] { + pub(crate) fn values(&self) -> [&RetainedStyleValueData; 4] { [ &self.transition_hint, &self.color, @@ -453,6 +563,7 @@ impl RetainedColorStopList { /// A retained named grid area: the retained area name and its grid line indices. #[repr(C)] +#[derive(PartialEq)] pub struct RetainedGridArea { name: RetainedUtf16FlyString, row_start: usize, @@ -472,9 +583,10 @@ retained_list!(RetainedGridAreaList, RetainedGridArea); /// A retained linear() easing stop: the output value and an optional input (null when absent). #[repr(C)] +#[derive(PartialEq)] pub struct RetainedLinearEasingStop { - output: RetainedStyleValue, - input: RetainedStyleValue, + output: RetainedStyleValueData, + input: RetainedStyleValueData, } /// A Rust-owned array of retained linear() easing stops. @@ -486,19 +598,30 @@ pub struct RetainedLinearEasingStopList { retained_list!(RetainedLinearEasingStopList, RetainedLinearEasingStop); +/// The kind of one grid track list entry. +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq)] +// The C++ constructor supplies every variant through the FFI input. +#[allow(dead_code)] +pub enum GridTrackEntryKind { + LineNames, + Size, + MinMax, + Repeat, +} + /// Borrowed input description of one grid track list entry, used when creating a grid track -/// size list. Kinds: 0 = line names, 1 = a single size, 2 = minmax, 3 = repeat with a nested -/// entry list. +/// size list. #[repr(C)] pub struct GridTrackEntryInput { - kind: u8, + kind: GridTrackEntryKind, names: *const usize, name_count: usize, - size_value: *const c_void, - min_value: *const c_void, - max_value: *const c_void, + size_value: *const StyleValueData, + min_value: *const StyleValueData, + max_value: *const StyleValueData, repeat_type: u8, - repeat_count: *const c_void, + repeat_count: *const StyleValueData, repeat_is_subgrid: bool, repeat_preserve_line_name_sets: bool, repeat_entries: *const GridTrackEntryInput, @@ -515,17 +638,17 @@ pub struct RetainedGridTrackEntryList { /// A retained, Rust-owned grid track list entry (see [`GridTrackEntryInput`] for the kinds). #[repr(C)] pub struct RetainedGridTrackEntry { - kind: u8, - names: RetainedUtf16FlyStringList, - size_value: RetainedStyleValue, - min_value: RetainedStyleValue, - max_value: RetainedStyleValue, - repeat_type: u8, - repeat_count: RetainedStyleValue, - repeat_is_subgrid: bool, - repeat_preserve_line_name_sets: bool, - repeat_entries_pointer: *mut RetainedGridTrackEntry, - repeat_entries_length: usize, + pub(crate) kind: GridTrackEntryKind, + pub(crate) names: RetainedUtf16FlyStringList, + pub(crate) size_value: RetainedStyleValueData, + pub(crate) min_value: RetainedStyleValueData, + pub(crate) max_value: RetainedStyleValueData, + pub(crate) repeat_type: u8, + pub(crate) repeat_count: RetainedStyleValueData, + pub(crate) repeat_is_subgrid: bool, + pub(crate) repeat_preserve_line_name_sets: bool, + pub(crate) repeat_entries_pointer: *mut RetainedGridTrackEntry, + pub(crate) repeat_entries_length: usize, } impl Drop for RetainedGridTrackEntry { @@ -541,7 +664,43 @@ impl Drop for RetainedGridTrackEntry { } } +impl PartialEq for RetainedGridTrackEntry { + fn eq(&self, other: &Self) -> bool { + let repeat_entries = |entry: &Self| { + if entry.repeat_entries_pointer.is_null() { + &[] + } else { + unsafe { std::slice::from_raw_parts(entry.repeat_entries_pointer, entry.repeat_entries_length) } + } + }; + self.kind == other.kind + && self.names == other.names + && self.size_value == other.size_value + && self.min_value == other.min_value + && self.max_value == other.max_value + && self.repeat_type == other.repeat_type + && self.repeat_count == other.repeat_count + && self.repeat_is_subgrid == other.repeat_is_subgrid + && self.repeat_preserve_line_name_sets == other.repeat_preserve_line_name_sets + && repeat_entries(self) == repeat_entries(other) + } +} + impl RetainedGridTrackEntryList { + pub(crate) fn as_slice(&self) -> &[RetainedGridTrackEntry] { + if self.pointer.is_null() { + return &[]; + } + unsafe { std::slice::from_raw_parts(self.pointer, self.length) } + } + + pub(crate) fn from_retained_entries(entries: Vec) -> Self { + let slice = entries.into_boxed_slice(); + let length = slice.len(); + let pointer = Box::into_raw(slice) as *mut RetainedGridTrackEntry; + Self { pointer, length } + } + /// Takes ownership of the entries' retained values and names, recursively for nested /// repeat lists. /// @@ -554,19 +713,11 @@ impl RetainedGridTrackEntryList { RetainedGridTrackEntry { kind: input.kind, names: unsafe { RetainedUtf16FlyStringList::from_raw(input.names, input.name_count) }, - size_value: RetainedStyleValue { - pointer: input.size_value, - }, - min_value: RetainedStyleValue { - pointer: input.min_value, - }, - max_value: RetainedStyleValue { - pointer: input.max_value, - }, + size_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(input.size_value) }, + min_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(input.min_value) }, + max_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(input.max_value) }, repeat_type: input.repeat_type, - repeat_count: RetainedStyleValue { - pointer: input.repeat_count, - }, + repeat_count: unsafe { RetainedStyleValueData::from_retained_optional_pointer(input.repeat_count) }, repeat_is_subgrid: input.repeat_is_subgrid, repeat_preserve_line_name_sets: input.repeat_preserve_line_name_sets, repeat_entries_pointer: { @@ -587,13 +738,23 @@ impl RetainedGridTrackEntryList { } } +impl RetainedGridTrackEntry { + pub(crate) fn repeat_entries(&self) -> &[RetainedGridTrackEntry] { + if self.repeat_entries_pointer.is_null() { + return &[]; + } + unsafe { std::slice::from_raw_parts(self.repeat_entries_pointer, self.repeat_entries_length) } + } +} + retained_list_drop!(RetainedGridTrackEntryList); /// A retained polygon point: the x and y style values. #[repr(C)] +#[derive(PartialEq)] pub struct RetainedShapePoint { - x: RetainedStyleValue, - y: RetainedStyleValue, + x: RetainedStyleValueData, + y: RetainedStyleValueData, } /// A Rust-owned array of retained polygon points. @@ -608,6 +769,7 @@ retained_list!(RetainedShapePointList, RetainedShapePoint); /// An accepted numeric range for one value type (the C++ `enum class ValueType : u8`, opaque /// to Rust). #[repr(C)] +#[derive(PartialEq)] pub struct RetainedNumericRangeByType { value_type: u8, min: f64, @@ -634,22 +796,77 @@ pub struct RetainedNumericRangeList { retained_list!(RetainedNumericRangeList, RetainedNumericRangeByType); impl RetainedNumericRangeList { + pub(crate) fn empty() -> Self { + Self { + pointer: std::ptr::null_mut(), + length: 0, + } + } + pub(crate) fn as_slice(&self) -> &[RetainedNumericRangeByType] { if self.pointer.is_null() { return &[]; } unsafe { std::slice::from_raw_parts(self.pointer, self.length) } } + + pub(crate) fn clone_owned(&self) -> Self { + let ranges = self + .as_slice() + .iter() + .map(|range| RetainedNumericRangeByType { + value_type: range.value_type, + min: range.min, + max: range.max, + }) + .collect::>() + .into_boxed_slice(); + let length = ranges.len(); + let pointer = Box::into_raw(ranges) as *mut RetainedNumericRangeByType; + Self { pointer, length } + } } +macro_rules! retained_list_partial_eq { + ($list:ty, $element:ty) => { + impl PartialEq for $list { + fn eq(&self, other: &Self) -> bool { + let as_slice = |list: &Self| -> &[$element] { + if list.pointer.is_null() { + &[] + } else { + unsafe { std::slice::from_raw_parts(list.pointer, list.length) } + } + }; + as_slice(self) == as_slice(other) + } + } + }; +} + +retained_list_partial_eq!(RetainedStyleValueDataList, RetainedStyleValueData); +retained_list_partial_eq!(RetainedPropertyIdList, u16); +retained_list_partial_eq!(RetainedRequestUrlModifierList, RetainedRequestUrlModifier); +retained_list_partial_eq!(RetainedByteList, u8); +retained_list_partial_eq!(RetainedUtf16FlyStringList, RetainedUtf16FlyString); +retained_list_partial_eq!(RetainedCounterDefinitionList, RetainedCounterDefinition); +retained_list_partial_eq!(RetainedImageSetOptionList, RetainedImageSetOption); +retained_list_partial_eq!(RetainedColorStopList, RetainedColorStop); +retained_list_partial_eq!(RetainedGridAreaList, RetainedGridArea); +retained_list_partial_eq!(RetainedLinearEasingStopList, RetainedLinearEasingStop); +retained_list_partial_eq!(RetainedGridTrackEntryList, RetainedGridTrackEntry); +retained_list_partial_eq!(RetainedShapePointList, RetainedShapePoint); +retained_list_partial_eq!(RetainedNumericRangeList, RetainedNumericRangeByType); + /// The shared leading fields of every color variant payload: the optional color type and the /// color syntax. Placing this first in each color payload lets C++ read it without knowing /// which color variant it has. #[repr(C)] +#[derive(PartialEq)] pub struct ColorBase { - has_color_type: bool, - color_type: u8, - color_syntax: u8, + pub(crate) has_color_type: bool, + pub(crate) color_type: u8, + pub(crate) color_syntax: u8, } /// The data of a single immutable CSS style value. @@ -659,6 +876,7 @@ pub struct ColorBase { #[repr(C, u8)] // NB: Variant payload fields are only read by C++ through the exposed layout. #[allow(dead_code)] +#[derive(PartialEq)] pub enum StyleValueData { /// A CSS keyword. The value is the generated C++ `enum class Keyword : u16`, opaque to Rust. Keyword { keyword: u16 }, @@ -685,18 +903,17 @@ pub enum StyleValueData { /// uses the fill rule and the retained serialized path data string. BasicShape { kind: u8, - v0: RetainedStyleValue, - v1: RetainedStyleValue, - v2: RetainedStyleValue, - v3: RetainedStyleValue, - v4: RetainedStyleValue, + v0: RetainedStyleValueData, + v1: RetainedStyleValueData, + v2: RetainedStyleValueData, + v3: RetainedStyleValueData, + v4: RetainedStyleValueData, fill_rule: u8, points: RetainedShapePointList, path_string: RetainedUtf16FlyString, }, - /// A calc() or other math function: the retained calculation node tree root, the resolved - /// numeric type as its raw bytes (a trivially copyable C++ NumericType, opaque to Rust) - /// and the parse-time calculation context. + /// A calc() or other math function: the retained calculation node tree root, its resolved + /// numeric type, and the parse-time calculation context. Calculated { rust_calculation: crate::calc::CalcNodeHandle, /// The resolve-against target, base-mapped at creation: whether one @@ -704,7 +921,7 @@ pub enum StyleValueData { /// index in the numeric type order. resolve_as_is_number: bool, resolve_as_base: u8, - resolved_type: RetainedByteList, + resolved_type: crate::calc::FfiNumericType, has_percentages_resolve_as: bool, percentages_resolve_as: u8, resolve_numbers_as_integers: bool, @@ -712,19 +929,19 @@ pub enum StyleValueData { }, /// A CSS ``, e.g. `16 / 9`. The numerator and denominator are style values. Ratio { - numerator: RetainedStyleValue, - denominator: RetainedStyleValue, + numerator: RetainedStyleValueData, + denominator: RetainedStyleValueData, }, /// A unicode-range, e.g. `U+0025-00FF`. UnicodeRange { min_code_point: u32, max_code_point: u32 }, /// A CSS ``: a number, percentage or calculated style value. - OpacityValue { value: RetainedStyleValue }, + OpacityValue { value: RetainedStyleValueData }, /// One edge of a CSS ``: an optional edge keyword (the C++ `enum class /// PositionEdge : u8`, opaque to Rust) and an optional offset style value (null when absent). Edge { has_edge: bool, edge: u8, - offset: RetainedStyleValue, + offset: RetainedStyleValueData, }, /// The guaranteed-invalid value: https://drafts.csswg.org/css-variables/#guaranteed-invalid-value GuaranteedInvalid, @@ -737,49 +954,50 @@ pub enum StyleValueData { /// contrast-color() with its retained color style value. ContrastColor { color_base: ColorBase, - color: RetainedStyleValue, + color: RetainedStyleValueData, }, /// superellipse() with its retained parameter style value. - Superellipse { parameter: RetainedStyleValue }, + Superellipse { parameter: RetainedStyleValueData }, /// A pending-substitution value retaining the shorthand value it came from. PendingSubstitution { - original_shorthand_value: RetainedStyleValue, + original_shorthand_value: RetainedStyleValueData, }, /// scrollbar-color with retained thumb and track color values. ScrollbarColor { - thumb_color: RetainedStyleValue, - track_color: RetainedStyleValue, + thumb_color: RetainedStyleValueData, + track_color: RetainedStyleValueData, }, /// rect() with four retained edge style values. Rect { - top: RetainedStyleValue, - right: RetainedStyleValue, - bottom: RetainedStyleValue, - left: RetainedStyleValue, + top: RetainedStyleValueData, + right: RetainedStyleValueData, + bottom: RetainedStyleValueData, + left: RetainedStyleValueData, }, /// A CSS ``. String { string: RetainedUtf16FlyString }, /// An unrecognized CSS function, kept as its name and argument value. Function { name: RetainedUtf16FlyString, - value: RetainedStyleValue, + value: RetainedStyleValueData, }, /// An OpenType tag with its value, from font-feature-settings or font-variation-settings. /// The mode is the C++ OpenTypeTaggedStyleValue::Mode, opaque to Rust. OpenTypeTagged { mode: u8, tag: RetainedUtf16FlyString, - value: RetainedStyleValue, + packed_tag: u32, + value: RetainedStyleValueData, }, /// font-style: a keyword (the C++ `enum class FontStyleKeyword : u8`, opaque to Rust) and /// an optional oblique angle style value (null when absent). FontStyle { font_style: u8, - angle_value: RetainedStyleValue, + angle_value: RetainedStyleValueData, }, /// text-indent: a length-percentage style value plus the hanging and each-line flags. TextIndent { - length_percentage: RetainedStyleValue, + length_percentage: RetainedStyleValueData, hanging: bool, each_line: bool, }, @@ -788,25 +1006,25 @@ pub enum StyleValueData { OverflowClipMargin { has_visual_box: bool, visual_box: u8, - offset: RetainedStyleValue, + offset: RetainedStyleValueData, }, /// sibling-count() or sibling-index(). Both fields are C++ `enum class ... : u8` values, /// opaque to Rust. TreeCountingFunction { function: u8, computed_type: u8 }, /// background-size with its two retained size style values. BackgroundSize { - size_x: RetainedStyleValue, - size_y: RetainedStyleValue, + size_x: RetainedStyleValueData, + size_y: RetainedStyleValueData, }, /// A background repeat-style. Both fields are the C++ `enum class Repetition : u8`, opaque /// to Rust. RepeatStyle { repeat_x: u8, repeat_y: u8 }, - /// border-image-slice: four retained offset style values and the fill keyword. + /// border-image-slice: four retained offset data allocations and the fill keyword. BorderImageSlice { - top: RetainedStyleValue, - right: RetainedStyleValue, - bottom: RetainedStyleValue, - left: RetainedStyleValue, + top: RetainedStyleValueData, + right: RetainedStyleValueData, + bottom: RetainedStyleValueData, + left: RetainedStyleValueData, fill: bool, }, /// anchor-size(): an optional anchor name, an optional size keyword (the C++ `enum class @@ -816,36 +1034,36 @@ pub enum StyleValueData { anchor_name: RetainedUtf16FlyString, has_anchor_size: bool, anchor_size: u8, - fallback_value: RetainedStyleValue, + fallback_value: RetainedStyleValueData, }, /// anchor(): an optional anchor name, the retained side style value and an optional /// retained fallback value. Anchor { has_anchor_name: bool, anchor_name: RetainedUtf16FlyString, - anchor_side: RetainedStyleValue, - fallback_value: RetainedStyleValue, + anchor_side: RetainedStyleValueData, + fallback_value: RetainedStyleValueData, }, /// A CSS `` with its two retained edge style values. Position { - edge_x: RetainedStyleValue, - edge_y: RetainedStyleValue, + edge_x: RetainedStyleValueData, + edge_y: RetainedStyleValueData, }, /// A shadow. The type and placement are C++ enums, opaque to Rust; the color, blur radius /// and spread distance are optional retained style values (null when absent). Shadow { shadow_type: u8, - color: RetainedStyleValue, - offset_x: RetainedStyleValue, - offset_y: RetainedStyleValue, - blur_radius: RetainedStyleValue, - spread_distance: RetainedStyleValue, + color: RetainedStyleValueData, + offset_x: RetainedStyleValueData, + offset_y: RetainedStyleValueData, + blur_radius: RetainedStyleValueData, + spread_distance: RetainedStyleValueData, placement: u8, }, /// content with its retained content list and optional alt-text list (null when absent). Content { - content: RetainedStyleValue, - alt_text: RetainedStyleValue, + content: RetainedStyleValueData, + alt_text: RetainedStyleValueData, }, /// A @counter-style system descriptor: a plain system keyword (kind 0, the C++ `enum class /// CounterStyleSystem : u8`, opaque to Rust), fixed with an optional retained first symbol @@ -853,7 +1071,7 @@ pub enum StyleValueData { CounterStyleSystem { kind: u8, system: u8, - first_symbol: RetainedStyleValue, + first_symbol: RetainedStyleValueData, name: RetainedUtf16FlyString, }, /// A counter style reference: either a retained counter style name, or a symbols() function @@ -870,23 +1088,23 @@ pub enum StyleValueData { /// color syntax. ColorFunction { color_base: ColorBase, - channel_0: RetainedStyleValue, - channel_1: RetainedStyleValue, - channel_2: RetainedStyleValue, - alpha: RetainedStyleValue, + channel_0: RetainedStyleValueData, + channel_1: RetainedStyleValueData, + channel_2: RetainedStyleValueData, + alpha: RetainedStyleValueData, has_name: bool, name: RetainedUtf16FlyString, - origin_color: RetainedStyleValue, + origin_color: RetainedStyleValueData, }, /// color-mix() with its optional retained interpolation method value and two components, /// each a retained color with an optional retained percentage. ColorMix { color_base: ColorBase, - color_interpolation_method: RetainedStyleValue, - first_color: RetainedStyleValue, - first_percentage: RetainedStyleValue, - second_color: RetainedStyleValue, - second_percentage: RetainedStyleValue, + color_interpolation_method: RetainedStyleValueData, + first_color: RetainedStyleValueData, + first_percentage: RetainedStyleValueData, + second_color: RetainedStyleValueData, + second_percentage: RetainedStyleValueData, }, /// The shared data of every color style value: an optional color type and the color syntax /// (both C++ enums on ColorStyleValue, opaque to Rust). @@ -895,23 +1113,23 @@ pub enum StyleValueData { /// retained interpolation method and the color syntax. Enums are C++ types, opaque to Rust. LinearGradient { has_direction_value: bool, - direction_value: RetainedStyleValue, + direction_value: RetainedStyleValueData, side_or_corner: u8, color_stop_list: RetainedColorStopList, gradient_type: u8, repeating: bool, - color_interpolation_method: RetainedStyleValue, + color_interpolation_method: RetainedStyleValueData, color_syntax: u8, }, /// conic-gradient(): an optional retained from-angle, the retained position, the retained /// color stops, the repeating flag, an optional retained interpolation method and the color /// syntax (a C++ enum, opaque to Rust). ConicGradient { - from_angle: RetainedStyleValue, - position: RetainedStyleValue, + from_angle: RetainedStyleValueData, + position: RetainedStyleValueData, color_stop_list: RetainedColorStopList, repeating: bool, - color_interpolation_method: RetainedStyleValue, + color_interpolation_method: RetainedStyleValueData, color_syntax: u8, }, /// radial-gradient(): the ending shape (a C++ enum, opaque to Rust), the retained size and @@ -919,11 +1137,11 @@ pub enum StyleValueData { /// interpolation method and the color syntax. RadialGradient { ending_shape: u8, - size: RetainedStyleValue, - position: RetainedStyleValue, + size: RetainedStyleValueData, + position: RetainedStyleValueData, color_stop_list: RetainedColorStopList, repeating: bool, - color_interpolation_method: RetainedStyleValue, + color_interpolation_method: RetainedStyleValueData, color_syntax: u8, }, /// A url() image. Only the CSS URL is immutable value data; the style sheet attachment and @@ -941,19 +1159,19 @@ pub enum StyleValueData { Easing { kind: u8, linear_stops: RetainedLinearEasingStopList, - x1: RetainedStyleValue, - y1: RetainedStyleValue, - x2: RetainedStyleValue, - y2: RetainedStyleValue, - number_of_intervals: RetainedStyleValue, + x1: RetainedStyleValueData, + y1: RetainedStyleValueData, + x2: RetainedStyleValueData, + y2: RetainedStyleValueData, + number_of_intervals: RetainedStyleValueData, step_position: u8, }, /// A cursor with its retained image value and optional retained hotspot coordinates (both /// null or both non-null). Cursor { - image: RetainedStyleValue, - x: RetainedStyleValue, - y: RetainedStyleValue, + image: RetainedStyleValueData, + x: RetainedStyleValueData, + y: RetainedStyleValueData, }, /// A grid track size list: the subgrid and preserve-line-name-sets flags and the retained /// track entries. @@ -976,7 +1194,7 @@ pub enum StyleValueData { /// optional retained line value and an optional retained name. GridTrackPlacement { kind: u8, - value: RetainedStyleValue, + value: RetainedStyleValueData, has_name: bool, name: RetainedUtf16FlyString, }, @@ -985,19 +1203,19 @@ pub enum StyleValueData { Counter { function: u8, counter_name: RetainedUtf16FlyString, - counter_style: RetainedStyleValue, + counter_style: RetainedStyleValueData, join_string: RetainedUtf16FlyString, }, /// light-dark() with its two retained color style values. LightDark { color_base: ColorBase, - light: RetainedStyleValue, - dark: RetainedStyleValue, + light: RetainedStyleValueData, + dark: RetainedStyleValueData, }, /// random-value-sharing: an optional retained fixed value (null when absent), the auto flag, /// an optional name and the element-shared flag. RandomValueSharing { - fixed_value: RetainedStyleValue, + fixed_value: RetainedStyleValueData, is_auto: bool, has_name: bool, name: RetainedUtf16FlyString, @@ -1016,12 +1234,12 @@ pub enum StyleValueData { /// A list of style values. The separator and collapsible flag come from the C++ /// StyleValueList enums, opaque to Rust. ValueList { - values: RetainedStyleValueList, + values: RetainedStyleValueDataList, separator: u8, collapsible: bool, }, /// A tuple of optional style values (null entries represent absent optionals). - Tuple { values: RetainedStyleValueList }, + Tuple { values: RetainedStyleValueDataList }, /// A display value: the raw bytes of the C++ Display value type (a tag plus a union of /// packed u8 enums), opaque to Rust. Display { raw: u32 }, @@ -1033,7 +1251,8 @@ pub enum StyleValueData { }, /// An unresolved value containing arbitrary substitution functions, kept as its retained /// source text, an optional normalized comparison text (empty when absent), the presence - /// flags of each substitution function and the attr-taint flag. + /// flags of each substitution function, the attr-taint flag, and an optional parsed value + /// cached for an attr()-tainted registered custom property. Unresolved { source_text: RetainedReadableString, value_comparison_text: RetainedReadableString, @@ -1044,6 +1263,7 @@ pub enum StyleValueData { presence_inherit: bool, presence_var: bool, contains_attr_tainted_values: bool, + parsed_value: RetainedStyleValueData, }, /// A CSS url() or src() with its retained URL string, type (the C++ URL::Type, opaque to /// Rust) and request URL modifiers. @@ -1057,7 +1277,7 @@ pub enum StyleValueData { /// technologies (C++ `enum class FontTech : u8`, opaque to Rust). FontSource { is_local: bool, - local_name: RetainedStyleValue, + local_name: RetainedStyleValueData, url: RetainedString, url_type: u8, url_modifiers: RetainedRequestUrlModifierList, @@ -1071,39 +1291,39 @@ pub enum StyleValueData { component_count: u8, is_extent_0: bool, extent_0: u8, - value_0: RetainedStyleValue, + value_0: RetainedStyleValueData, is_extent_1: bool, extent_1: u8, - value_1: RetainedStyleValue, + value_1: RetainedStyleValueData, }, /// A transform function with its argument values. The property (PropertyID : u16) and /// function are C++ enums, opaque to Rust. Transformation { property: u16, transform_function: u8, - values: RetainedStyleValueList, + values: RetainedStyleValueDataList, }, /// A shorthand property value: the shorthand id, its longhand ids (both C++ /// `enum class PropertyID : u16`, opaque to Rust) and their values. Shorthand { shorthand_property: u16, sub_properties: RetainedPropertyIdList, - values: RetainedStyleValueList, + values: RetainedStyleValueDataList, }, /// A CSS ``. CustomIdent { custom_ident: RetainedUtf16FlyString }, - /// A border-radius rect of four retained corner radius style values. + /// A border-radius rect of four retained corner radius data allocations. BorderRadiusRect { - top_left: RetainedStyleValue, - top_right: RetainedStyleValue, - bottom_right: RetainedStyleValue, - bottom_left: RetainedStyleValue, + top_left: RetainedStyleValueData, + top_right: RetainedStyleValueData, + bottom_right: RetainedStyleValueData, + bottom_left: RetainedStyleValueData, }, /// A single corner radius: the horizontal and vertical radii and whether they differ. BorderRadius { is_elliptical: bool, - horizontal_radius: RetainedStyleValue, - vertical_radius: RetainedStyleValue, + horizontal_radius: RetainedStyleValueData, + vertical_radius: RetainedStyleValueData, }, /// A filter function. Kinds: blur (0, value = radius), drop-shadow (1, value = shadow), /// hue-rotate (2, value = angle), color (3, value = amount, with the color operation). @@ -1111,7 +1331,7 @@ pub enum StyleValueData { Filter { kind: u8, color_operation: u8, - value: RetainedStyleValue, + value: RetainedStyleValueData, }, } @@ -1160,65 +1380,65 @@ impl StyleValueData { } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_keyword(keyword: u16) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Keyword { keyword }))) +pub extern "C" fn rust_style_value_create_keyword(keyword: u16) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Keyword { keyword }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_number(value: f64) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Number { value }))) +pub extern "C" fn rust_style_value_create_number(value: f64) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Number { value }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_integer(value: i32) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Integer { value }))) +pub extern "C" fn rust_style_value_create_integer(value: i32) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Integer { value }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_angle(value: f64, unit: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Angle { value, unit }))) +pub extern "C" fn rust_style_value_create_angle(value: f64, unit: u8) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Angle { value, unit }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_flex(value: f64, unit: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Flex { value, unit }))) +pub extern "C" fn rust_style_value_create_flex(value: f64, unit: u8) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Flex { value, unit }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_frequency(value: f64, unit: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Frequency { value, unit }))) +pub extern "C" fn rust_style_value_create_frequency(value: f64, unit: u8) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Frequency { value, unit }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_length(value: f64, unit: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Length { value, unit }))) +pub extern "C" fn rust_style_value_create_length(value: f64, unit: u8) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Length { value, unit }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_percentage(value: f64) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Percentage { value }))) +pub extern "C" fn rust_style_value_create_percentage(value: f64) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Percentage { value }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_resolution(value: f64, unit: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Resolution { value, unit }))) +pub extern "C" fn rust_style_value_create_resolution(value: f64, unit: u8) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Resolution { value, unit }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_time(value: f64, unit: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Time { value, unit }))) +pub extern "C" fn rust_style_value_create_time(value: f64, unit: u8) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Time { value, unit }))) } /// Takes ownership of one strong reference to each of the numerator and denominator. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_ratio( - numerator: *const c_void, - denominator: *const c_void, -) -> *mut StyleValueData { + numerator: *const StyleValueData, + denominator: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Ratio { - numerator: RetainedStyleValue { pointer: numerator }, - denominator: RetainedStyleValue { pointer: denominator }, + Arc::into_raw(Arc::new(StyleValueData::Ratio { + numerator: unsafe { RetainedStyleValueData::from_retained_pointer(numerator) }, + denominator: unsafe { RetainedStyleValueData::from_retained_pointer(denominator) }, })) }) } @@ -1227,9 +1447,9 @@ pub unsafe extern "C" fn rust_style_value_create_ratio( pub extern "C" fn rust_style_value_create_unicode_range( min_code_point: u32, max_code_point: u32, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::UnicodeRange { + Arc::into_raw(Arc::new(StyleValueData::UnicodeRange { min_code_point, max_code_point, })) @@ -1238,48 +1458,51 @@ pub extern "C" fn rust_style_value_create_unicode_range( /// Takes ownership of one strong reference to the value. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_style_value_create_opacity_value(value: *const c_void) -> *mut StyleValueData { +pub unsafe extern "C" fn rust_style_value_create_opacity_value(value: *const StyleValueData) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::OpacityValue { - value: RetainedStyleValue { pointer: value }, + Arc::into_raw(Arc::new(StyleValueData::OpacityValue { + value: unsafe { RetainedStyleValueData::from_retained_pointer(value) }, })) }) } -/// Takes ownership of one strong reference to the offset if it is non-null. +/// Takes ownership of one strong reference to the offset data if it is non-null. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_edge( has_edge: bool, edge: u8, - offset: *const c_void, -) -> *mut StyleValueData { + offset: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Edge { + Arc::into_raw(Arc::new(StyleValueData::Edge { has_edge, edge, - offset: RetainedStyleValue { pointer: offset }, + offset: unsafe { RetainedStyleValueData::from_retained_optional_pointer(offset) }, })) }) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_guaranteed_invalid() -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::GuaranteedInvalid))) +pub extern "C" fn rust_style_value_create_guaranteed_invalid() -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::GuaranteedInvalid))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_empty_optional() -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::EmptyOptional))) +pub extern "C" fn rust_style_value_create_empty_optional() -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::EmptyOptional))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_grid_auto_flow(row: bool, dense: bool) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::GridAutoFlow { row, dense }))) +pub extern "C" fn rust_style_value_create_grid_auto_flow(row: bool, dense: bool) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::GridAutoFlow { row, dense }))) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_text_underline_position(horizontal: u8, vertical: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::TextUnderlinePosition { horizontal, vertical }))) +pub extern "C" fn rust_style_value_create_text_underline_position( + horizontal: u8, + vertical: u8, +) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::TextUnderlinePosition { horizontal, vertical }))) } /// Takes ownership of one strong reference to the color. @@ -1288,26 +1511,28 @@ pub unsafe extern "C" fn rust_style_value_create_contrast_color( has_color_type: bool, color_type: u8, color_syntax: u8, - color: *const c_void, -) -> *mut StyleValueData { + color: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ContrastColor { + Arc::into_raw(Arc::new(StyleValueData::ContrastColor { color_base: ColorBase { has_color_type, color_type, color_syntax, }, - color: RetainedStyleValue { pointer: color }, + color: unsafe { RetainedStyleValueData::from_retained_pointer(color) }, })) }) } -/// Takes ownership of one strong reference to the parameter. +/// Takes ownership of one strong reference to the parameter data. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_style_value_create_superellipse(parameter: *const c_void) -> *mut StyleValueData { +pub unsafe extern "C" fn rust_style_value_create_superellipse( + parameter: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Superellipse { - parameter: RetainedStyleValue { pointer: parameter }, + Arc::into_raw(Arc::new(StyleValueData::Superellipse { + parameter: unsafe { RetainedStyleValueData::from_retained_pointer(parameter) }, })) }) } @@ -1315,12 +1540,12 @@ pub unsafe extern "C" fn rust_style_value_create_superellipse(parameter: *const /// Takes ownership of one strong reference to the original shorthand value. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_pending_substitution( - original_shorthand_value: *const c_void, -) -> *mut StyleValueData { + original_shorthand_value: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::PendingSubstitution { - original_shorthand_value: RetainedStyleValue { - pointer: original_shorthand_value, + Arc::into_raw(Arc::new(StyleValueData::PendingSubstitution { + original_shorthand_value: unsafe { + RetainedStyleValueData::from_retained_pointer(original_shorthand_value) }, })) }) @@ -1329,31 +1554,31 @@ pub unsafe extern "C" fn rust_style_value_create_pending_substitution( /// Takes ownership of one strong reference to each color. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_scrollbar_color( - thumb_color: *const c_void, - track_color: *const c_void, -) -> *mut StyleValueData { + thumb_color: *const StyleValueData, + track_color: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ScrollbarColor { - thumb_color: RetainedStyleValue { pointer: thumb_color }, - track_color: RetainedStyleValue { pointer: track_color }, + Arc::into_raw(Arc::new(StyleValueData::ScrollbarColor { + thumb_color: unsafe { RetainedStyleValueData::from_retained_pointer(thumb_color) }, + track_color: unsafe { RetainedStyleValueData::from_retained_pointer(track_color) }, })) }) } -/// Takes ownership of one strong reference to each edge. +/// Takes ownership of one strong reference to each edge data allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_rect( - top: *const c_void, - right: *const c_void, - bottom: *const c_void, - left: *const c_void, -) -> *mut StyleValueData { + top: *const StyleValueData, + right: *const StyleValueData, + bottom: *const StyleValueData, + left: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Rect { - top: RetainedStyleValue { pointer: top }, - right: RetainedStyleValue { pointer: right }, - bottom: RetainedStyleValue { pointer: bottom }, - left: RetainedStyleValue { pointer: left }, + Arc::into_raw(Arc::new(StyleValueData::Rect { + top: unsafe { RetainedStyleValueData::from_retained_pointer(top) }, + right: unsafe { RetainedStyleValueData::from_retained_pointer(right) }, + bottom: unsafe { RetainedStyleValueData::from_retained_pointer(bottom) }, + left: unsafe { RetainedStyleValueData::from_retained_pointer(left) }, })) }) } @@ -1363,60 +1588,56 @@ pub unsafe extern "C" fn rust_style_value_create_rect( pub unsafe extern "C" fn rust_style_value_create_filter( kind: u8, color_operation: u8, - value: *const c_void, -) -> *mut StyleValueData { + value: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Filter { + Arc::into_raw(Arc::new(StyleValueData::Filter { kind, color_operation, - value: RetainedStyleValue { pointer: value }, + value: unsafe { RetainedStyleValueData::from_retained_pointer(value) }, })) }) } -/// Takes ownership of one strong reference to each radius. +/// Takes ownership of one strong reference to each radius data allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_border_radius( is_elliptical: bool, - horizontal_radius: *const c_void, - vertical_radius: *const c_void, -) -> *mut StyleValueData { + horizontal_radius: *const StyleValueData, + vertical_radius: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::BorderRadius { + Arc::into_raw(Arc::new(StyleValueData::BorderRadius { is_elliptical, - horizontal_radius: RetainedStyleValue { - pointer: horizontal_radius, - }, - vertical_radius: RetainedStyleValue { - pointer: vertical_radius, - }, + horizontal_radius: unsafe { RetainedStyleValueData::from_retained_pointer(horizontal_radius) }, + vertical_radius: unsafe { RetainedStyleValueData::from_retained_pointer(vertical_radius) }, })) }) } -/// Takes ownership of one strong reference to each corner radius. +/// Takes ownership of one strong reference to each corner radius data allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_border_radius_rect( - top_left: *const c_void, - top_right: *const c_void, - bottom_right: *const c_void, - bottom_left: *const c_void, -) -> *mut StyleValueData { + top_left: *const StyleValueData, + top_right: *const StyleValueData, + bottom_right: *const StyleValueData, + bottom_left: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::BorderRadiusRect { - top_left: RetainedStyleValue { pointer: top_left }, - top_right: RetainedStyleValue { pointer: top_right }, - bottom_right: RetainedStyleValue { pointer: bottom_right }, - bottom_left: RetainedStyleValue { pointer: bottom_left }, + Arc::into_raw(Arc::new(StyleValueData::BorderRadiusRect { + top_left: unsafe { RetainedStyleValueData::from_retained_pointer(top_left) }, + top_right: unsafe { RetainedStyleValueData::from_retained_pointer(top_right) }, + bottom_right: unsafe { RetainedStyleValueData::from_retained_pointer(bottom_right) }, + bottom_left: unsafe { RetainedStyleValueData::from_retained_pointer(bottom_left) }, })) }) } /// Takes ownership of one leaked reference to the string. #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_string(string: usize) -> *mut StyleValueData { +pub extern "C" fn rust_style_value_create_string(string: usize) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::String { + Arc::into_raw(Arc::new(StyleValueData::String { string: RetainedUtf16FlyString { raw: string }, })) }) @@ -1424,51 +1645,56 @@ pub extern "C" fn rust_style_value_create_string(string: usize) -> *mut StyleVal /// Takes ownership of one leaked reference to the string. #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_custom_ident(custom_ident: usize) -> *mut StyleValueData { +pub extern "C" fn rust_style_value_create_custom_ident(custom_ident: usize) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::CustomIdent { + Arc::into_raw(Arc::new(StyleValueData::CustomIdent { custom_ident: RetainedUtf16FlyString { raw: custom_ident }, })) }) } -/// Takes ownership of one leaked reference to the name and one strong reference to the value. +/// Takes ownership of one leaked reference to the name and one strong reference to the value data. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_style_value_create_function(name: usize, value: *const c_void) -> *mut StyleValueData { +pub unsafe extern "C" fn rust_style_value_create_function( + name: usize, + value: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Function { + Arc::into_raw(Arc::new(StyleValueData::Function { name: RetainedUtf16FlyString { raw: name }, - value: RetainedStyleValue { pointer: value }, + value: unsafe { RetainedStyleValueData::from_retained_pointer(value) }, })) }) } -/// Takes ownership of one leaked reference to the tag and one strong reference to the value. +/// Takes ownership of one leaked reference to the tag and one strong reference to the value data. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_open_type_tagged( mode: u8, tag: usize, - value: *const c_void, -) -> *mut StyleValueData { + packed_tag: u32, + value: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::OpenTypeTagged { + Arc::into_raw(Arc::new(StyleValueData::OpenTypeTagged { mode, tag: RetainedUtf16FlyString { raw: tag }, - value: RetainedStyleValue { pointer: value }, + packed_tag, + value: unsafe { RetainedStyleValueData::from_retained_pointer(value) }, })) }) } -/// Takes ownership of one strong reference to the angle value if it is non-null. +/// Takes ownership of one strong reference to the angle value data if it is non-null. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_font_style( font_style: u8, - angle_value: *const c_void, -) -> *mut StyleValueData { + angle_value: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::FontStyle { + Arc::into_raw(Arc::new(StyleValueData::FontStyle { font_style, - angle_value: RetainedStyleValue { pointer: angle_value }, + angle_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(angle_value) }, })) }) } @@ -1476,33 +1702,31 @@ pub unsafe extern "C" fn rust_style_value_create_font_style( /// Takes ownership of one strong reference to the length-percentage. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_text_indent( - length_percentage: *const c_void, + length_percentage: *const StyleValueData, hanging: bool, each_line: bool, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::TextIndent { - length_percentage: RetainedStyleValue { - pointer: length_percentage, - }, + Arc::into_raw(Arc::new(StyleValueData::TextIndent { + length_percentage: unsafe { RetainedStyleValueData::from_retained_pointer(length_percentage) }, hanging, each_line, })) }) } -/// Takes ownership of one strong reference to the offset. +/// Takes ownership of one strong reference to the offset data. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_overflow_clip_margin( has_visual_box: bool, visual_box: u8, - offset: *const c_void, -) -> *mut StyleValueData { + offset: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::OverflowClipMargin { + Arc::into_raw(Arc::new(StyleValueData::OverflowClipMargin { has_visual_box, visual_box, - offset: RetainedStyleValue { pointer: offset }, + offset: unsafe { RetainedStyleValueData::from_retained_pointer(offset) }, })) }) } @@ -1511,9 +1735,9 @@ pub unsafe extern "C" fn rust_style_value_create_overflow_clip_margin( pub extern "C" fn rust_style_value_create_tree_counting_function( function: u8, computed_type: u8, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::TreeCountingFunction { + Arc::into_raw(Arc::new(StyleValueData::TreeCountingFunction { function, computed_type, })) @@ -1523,37 +1747,37 @@ pub extern "C" fn rust_style_value_create_tree_counting_function( /// Takes ownership of one strong reference to each size. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_background_size( - size_x: *const c_void, - size_y: *const c_void, -) -> *mut StyleValueData { + size_x: *const StyleValueData, + size_y: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::BackgroundSize { - size_x: RetainedStyleValue { pointer: size_x }, - size_y: RetainedStyleValue { pointer: size_y }, + Arc::into_raw(Arc::new(StyleValueData::BackgroundSize { + size_x: unsafe { RetainedStyleValueData::from_retained_pointer(size_x) }, + size_y: unsafe { RetainedStyleValueData::from_retained_pointer(size_y) }, })) }) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_repeat_style(repeat_x: u8, repeat_y: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::RepeatStyle { repeat_x, repeat_y }))) +pub extern "C" fn rust_style_value_create_repeat_style(repeat_x: u8, repeat_y: u8) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::RepeatStyle { repeat_x, repeat_y }))) } -/// Takes ownership of one strong reference to each offset. +/// Takes ownership of one strong reference to each offset data allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_border_image_slice( - top: *const c_void, - right: *const c_void, - bottom: *const c_void, - left: *const c_void, + top: *const StyleValueData, + right: *const StyleValueData, + bottom: *const StyleValueData, + left: *const StyleValueData, fill: bool, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::BorderImageSlice { - top: RetainedStyleValue { pointer: top }, - right: RetainedStyleValue { pointer: right }, - bottom: RetainedStyleValue { pointer: bottom }, - left: RetainedStyleValue { pointer: left }, + Arc::into_raw(Arc::new(StyleValueData::BorderImageSlice { + top: unsafe { RetainedStyleValueData::from_retained_pointer(top) }, + right: unsafe { RetainedStyleValueData::from_retained_pointer(right) }, + bottom: unsafe { RetainedStyleValueData::from_retained_pointer(bottom) }, + left: unsafe { RetainedStyleValueData::from_retained_pointer(left) }, fill, })) }) @@ -1567,17 +1791,15 @@ pub unsafe extern "C" fn rust_style_value_create_anchor_size( anchor_name: usize, has_anchor_size: bool, anchor_size: u8, - fallback_value: *const c_void, -) -> *mut StyleValueData { + fallback_value: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::AnchorSize { + Arc::into_raw(Arc::new(StyleValueData::AnchorSize { has_anchor_name, anchor_name: RetainedUtf16FlyString { raw: anchor_name }, has_anchor_size, anchor_size, - fallback_value: RetainedStyleValue { - pointer: fallback_value, - }, + fallback_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(fallback_value) }, })) }) } @@ -1588,31 +1810,29 @@ pub unsafe extern "C" fn rust_style_value_create_anchor_size( pub unsafe extern "C" fn rust_style_value_create_anchor( has_anchor_name: bool, anchor_name: usize, - anchor_side: *const c_void, - fallback_value: *const c_void, -) -> *mut StyleValueData { + anchor_side: *const StyleValueData, + fallback_value: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Anchor { + Arc::into_raw(Arc::new(StyleValueData::Anchor { has_anchor_name, anchor_name: RetainedUtf16FlyString { raw: anchor_name }, - anchor_side: RetainedStyleValue { pointer: anchor_side }, - fallback_value: RetainedStyleValue { - pointer: fallback_value, - }, + anchor_side: unsafe { RetainedStyleValueData::from_retained_pointer(anchor_side) }, + fallback_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(fallback_value) }, })) }) } -/// Takes ownership of one strong reference to each edge. +/// Takes ownership of one strong reference to each edge data allocation. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_position( - edge_x: *const c_void, - edge_y: *const c_void, -) -> *mut StyleValueData { + edge_x: *const StyleValueData, + edge_y: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Position { - edge_x: RetainedStyleValue { pointer: edge_x }, - edge_y: RetainedStyleValue { pointer: edge_y }, + Arc::into_raw(Arc::new(StyleValueData::Position { + edge_x: unsafe { RetainedStyleValueData::from_retained_pointer(edge_x) }, + edge_y: unsafe { RetainedStyleValueData::from_retained_pointer(edge_y) }, })) }) } @@ -1621,23 +1841,21 @@ pub unsafe extern "C" fn rust_style_value_create_position( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_shadow( shadow_type: u8, - color: *const c_void, - offset_x: *const c_void, - offset_y: *const c_void, - blur_radius: *const c_void, - spread_distance: *const c_void, + color: *const StyleValueData, + offset_x: *const StyleValueData, + offset_y: *const StyleValueData, + blur_radius: *const StyleValueData, + spread_distance: *const StyleValueData, placement: u8, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Shadow { + Arc::into_raw(Arc::new(StyleValueData::Shadow { shadow_type, - color: RetainedStyleValue { pointer: color }, - offset_x: RetainedStyleValue { pointer: offset_x }, - offset_y: RetainedStyleValue { pointer: offset_y }, - blur_radius: RetainedStyleValue { pointer: blur_radius }, - spread_distance: RetainedStyleValue { - pointer: spread_distance, - }, + color: unsafe { RetainedStyleValueData::from_retained_optional_pointer(color) }, + offset_x: unsafe { RetainedStyleValueData::from_retained_pointer(offset_x) }, + offset_y: unsafe { RetainedStyleValueData::from_retained_pointer(offset_y) }, + blur_radius: unsafe { RetainedStyleValueData::from_retained_optional_pointer(blur_radius) }, + spread_distance: unsafe { RetainedStyleValueData::from_retained_optional_pointer(spread_distance) }, placement, })) }) @@ -1647,13 +1865,13 @@ pub unsafe extern "C" fn rust_style_value_create_shadow( /// alt-text list. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_content( - content: *const c_void, - alt_text: *const c_void, -) -> *mut StyleValueData { + content: *const StyleValueData, + alt_text: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Content { - content: RetainedStyleValue { pointer: content }, - alt_text: RetainedStyleValue { pointer: alt_text }, + Arc::into_raw(Arc::new(StyleValueData::Content { + content: unsafe { RetainedStyleValueData::from_retained_pointer(content) }, + alt_text: unsafe { RetainedStyleValueData::from_retained_optional_pointer(alt_text) }, })) }) } @@ -1664,14 +1882,14 @@ pub unsafe extern "C" fn rust_style_value_create_content( pub unsafe extern "C" fn rust_style_value_create_counter( function: u8, counter_name: usize, - counter_style: *const c_void, + counter_style: *const StyleValueData, join_string: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Counter { + Arc::into_raw(Arc::new(StyleValueData::Counter { function, counter_name: RetainedUtf16FlyString { raw: counter_name }, - counter_style: RetainedStyleValue { pointer: counter_style }, + counter_style: unsafe { RetainedStyleValueData::from_retained_pointer(counter_style) }, join_string: RetainedUtf16FlyString { raw: join_string }, })) }) @@ -1683,18 +1901,18 @@ pub unsafe extern "C" fn rust_style_value_create_light_dark( has_color_type: bool, color_type: u8, color_syntax: u8, - light: *const c_void, - dark: *const c_void, -) -> *mut StyleValueData { + light: *const StyleValueData, + dark: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::LightDark { + Arc::into_raw(Arc::new(StyleValueData::LightDark { color_base: ColorBase { has_color_type, color_type, color_syntax, }, - light: RetainedStyleValue { pointer: light }, - dark: RetainedStyleValue { pointer: dark }, + light: unsafe { RetainedStyleValueData::from_retained_pointer(light) }, + dark: unsafe { RetainedStyleValueData::from_retained_pointer(dark) }, })) }) } @@ -1703,15 +1921,15 @@ pub unsafe extern "C" fn rust_style_value_create_light_dark( /// name when they are present. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_random_value_sharing( - fixed_value: *const c_void, + fixed_value: *const StyleValueData, is_auto: bool, has_name: bool, name: usize, element_shared: bool, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::RandomValueSharing { - fixed_value: RetainedStyleValue { pointer: fixed_value }, + Arc::into_raw(Arc::new(StyleValueData::RandomValueSharing { + fixed_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(fixed_value) }, is_auto, has_name, name: RetainedUtf16FlyString { raw: name }, @@ -1721,8 +1939,8 @@ pub unsafe extern "C" fn rust_style_value_create_random_value_sharing( } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_scrollbar_gutter(value: u8) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::ScrollbarGutter { value }))) +pub extern "C" fn rust_style_value_create_scrollbar_gutter(value: u8) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::ScrollbarGutter { value }))) } #[unsafe(no_mangle)] @@ -1730,9 +1948,9 @@ pub extern "C" fn rust_style_value_create_color_interpolation_method( is_polar: bool, color_space: u8, hue_interpolation_method: u8, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ColorInterpolationMethod { + Arc::into_raw(Arc::new(StyleValueData::ColorInterpolationMethod { is_polar, color_space, hue_interpolation_method, @@ -1743,14 +1961,14 @@ pub extern "C" fn rust_style_value_create_color_interpolation_method( /// Takes ownership of one strong reference to each of the `length` values. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_value_list( - values: *const *const c_void, + values: *const *const StyleValueData, length: usize, separator: u8, collapsible: bool, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ValueList { - values: unsafe { RetainedStyleValueList::from_raw(values, length) }, + Arc::into_raw(Arc::new(StyleValueData::ValueList { + values: unsafe { RetainedStyleValueDataList::from_retained_pointers(values, length) }, separator, collapsible, })) @@ -1760,12 +1978,12 @@ pub unsafe extern "C" fn rust_style_value_create_value_list( /// Takes ownership of one strong reference to each non-null value. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_tuple( - values: *const *const c_void, + values: *const *const StyleValueData, length: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Tuple { - values: unsafe { RetainedStyleValueList::from_raw(values, length) }, + Arc::into_raw(Arc::new(StyleValueData::Tuple { + values: unsafe { RetainedStyleValueDataList::from_retained_optional_pointers(values, length) }, })) }) } @@ -1775,14 +1993,14 @@ pub unsafe extern "C" fn rust_style_value_create_tuple( pub unsafe extern "C" fn rust_style_value_create_transformation( property: u16, transform_function: u8, - values: *const *const c_void, + values: *const *const StyleValueData, length: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Transformation { + Arc::into_raw(Arc::new(StyleValueData::Transformation { property, transform_function, - values: unsafe { RetainedStyleValueList::from_raw(values, length) }, + values: unsafe { RetainedStyleValueDataList::from_retained_pointers(values, length) }, })) }) } @@ -1793,21 +2011,21 @@ pub unsafe extern "C" fn rust_style_value_create_shorthand( shorthand_property: u16, sub_properties: *const u16, sub_property_count: usize, - values: *const *const c_void, + values: *const *const StyleValueData, value_count: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Shorthand { + Arc::into_raw(Arc::new(StyleValueData::Shorthand { shorthand_property, sub_properties: unsafe { RetainedPropertyIdList::from_raw(sub_properties, sub_property_count) }, - values: unsafe { RetainedStyleValueList::from_raw(values, value_count) }, + values: unsafe { RetainedStyleValueDataList::from_retained_pointers(values, value_count) }, })) }) } #[unsafe(no_mangle)] -pub extern "C" fn rust_style_value_create_display(raw: u32) -> *mut StyleValueData { - abort_on_panic(|| Box::into_raw(Box::new(StyleValueData::Display { raw }))) +pub extern "C" fn rust_style_value_create_display(raw: u32) -> *const StyleValueData { + abort_on_panic(|| Arc::into_raw(Arc::new(StyleValueData::Display { raw }))) } /// Takes ownership of one strong reference to each non-null component value. @@ -1816,20 +2034,20 @@ pub unsafe extern "C" fn rust_style_value_create_radial_size( component_count: u8, is_extent_0: bool, extent_0: u8, - value_0: *const c_void, + value_0: *const StyleValueData, is_extent_1: bool, extent_1: u8, - value_1: *const c_void, -) -> *mut StyleValueData { + value_1: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::RadialSize { + Arc::into_raw(Arc::new(StyleValueData::RadialSize { component_count, is_extent_0, extent_0, - value_0: RetainedStyleValue { pointer: value_0 }, + value_0: unsafe { RetainedStyleValueData::from_retained_optional_pointer(value_0) }, is_extent_1, extent_1, - value_1: RetainedStyleValue { pointer: value_1 }, + value_1: unsafe { RetainedStyleValueData::from_retained_optional_pointer(value_1) }, })) }) } @@ -1839,13 +2057,15 @@ pub unsafe extern "C" fn rust_style_value_create_radial_size( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_url( url: usize, + url_bytes: *const u8, + url_length: usize, url_type: u8, modifiers: *const RetainedRequestUrlModifier, modifier_count: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Url { - url: RetainedString { raw: url }, + Arc::into_raw(Arc::new(StyleValueData::Url { + url: unsafe { RetainedString::from_raw(url, url_bytes, url_length) }, url_type, modifiers: unsafe { RetainedRequestUrlModifierList::from_raw(modifiers, modifier_count) }, })) @@ -1857,8 +2077,10 @@ pub unsafe extern "C" fn rust_style_value_create_url( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_font_source( is_local: bool, - local_name: *const c_void, + local_name: *const StyleValueData, url: usize, + url_bytes: *const u8, + url_length: usize, url_type: u8, url_modifiers: *const RetainedRequestUrlModifier, url_modifier_count: usize, @@ -1866,12 +2088,12 @@ pub unsafe extern "C" fn rust_style_value_create_font_source( format: usize, tech: *const u8, tech_count: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::FontSource { + Arc::into_raw(Arc::new(StyleValueData::FontSource { is_local, - local_name: RetainedStyleValue { pointer: local_name }, - url: RetainedString { raw: url }, + local_name: unsafe { RetainedStyleValueData::from_retained_optional_pointer(local_name) }, + url: unsafe { RetainedString::from_raw(url, url_bytes, url_length) }, url_type, url_modifiers: unsafe { RetainedRequestUrlModifierList::from_raw(url_modifiers, url_modifier_count) }, has_format, @@ -1888,9 +2110,9 @@ pub unsafe extern "C" fn rust_style_value_create_color_scheme( scheme_codes: *const u8, scheme_count: usize, only: bool, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ColorScheme { + Arc::into_raw(Arc::new(StyleValueData::ColorScheme { schemes: unsafe { RetainedUtf16FlyStringList::from_raw(schemes, scheme_count) }, scheme_codes: unsafe { RetainedByteList::from_raw(scheme_codes, scheme_count) }, only, @@ -1914,9 +2136,10 @@ pub unsafe extern "C" fn rust_style_value_create_unresolved( presence_inherit: bool, presence_var: bool, contains_attr_tainted_values: bool, -) -> *mut StyleValueData { + parsed_value: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Unresolved { + Arc::into_raw(Arc::new(StyleValueData::Unresolved { source_text: unsafe { RetainedReadableString::from_raw(source_text, source_text_bytes, source_text_length) }, @@ -1934,6 +2157,7 @@ pub unsafe extern "C" fn rust_style_value_create_unresolved( presence_inherit, presence_var, contains_attr_tainted_values, + parsed_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(parsed_value) }, })) }) } @@ -1943,9 +2167,9 @@ pub unsafe extern "C" fn rust_style_value_create_unresolved( pub unsafe extern "C" fn rust_style_value_create_counter_definitions( definitions: *const RetainedCounterDefinition, length: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::CounterDefinitions { + Arc::into_raw(Arc::new(StyleValueData::CounterDefinitions { counter_definitions: unsafe { RetainedCounterDefinitionList::from_raw(definitions, length) }, })) }) @@ -1956,14 +2180,14 @@ pub unsafe extern "C" fn rust_style_value_create_counter_definitions( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_grid_track_placement( kind: u8, - value: *const c_void, + value: *const StyleValueData, has_name: bool, name: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::GridTrackPlacement { + Arc::into_raw(Arc::new(StyleValueData::GridTrackPlacement { kind, - value: RetainedStyleValue { pointer: value }, + value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(value) }, has_name, name: RetainedUtf16FlyString { raw: name }, })) @@ -1976,14 +2200,14 @@ pub unsafe extern "C" fn rust_style_value_create_grid_track_placement( pub unsafe extern "C" fn rust_style_value_create_counter_style_system( kind: u8, system: u8, - first_symbol: *const c_void, + first_symbol: *const StyleValueData, name: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::CounterStyleSystem { + Arc::into_raw(Arc::new(StyleValueData::CounterStyleSystem { kind, system, - first_symbol: RetainedStyleValue { pointer: first_symbol }, + first_symbol: unsafe { RetainedStyleValueData::from_retained_optional_pointer(first_symbol) }, name: RetainedUtf16FlyString { raw: name }, })) }) @@ -1998,9 +2222,9 @@ pub unsafe extern "C" fn rust_style_value_create_counter_style( symbols_type: u8, symbols: *const usize, symbol_count: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::CounterStyle { + Arc::into_raw(Arc::new(StyleValueData::CounterStyle { is_symbols, name: RetainedUtf16FlyString { raw: name }, symbols_type, @@ -2012,15 +2236,15 @@ pub unsafe extern "C" fn rust_style_value_create_counter_style( /// Takes ownership of one strong reference to the image and to each non-null coordinate. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_cursor( - image: *const c_void, - x: *const c_void, - y: *const c_void, -) -> *mut StyleValueData { + image: *const StyleValueData, + x: *const StyleValueData, + y: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Cursor { - image: RetainedStyleValue { pointer: image }, - x: RetainedStyleValue { pointer: x }, - y: RetainedStyleValue { pointer: y }, + Arc::into_raw(Arc::new(StyleValueData::Cursor { + image: unsafe { RetainedStyleValueData::from_retained_pointer(image) }, + x: unsafe { RetainedStyleValueData::from_retained_optional_pointer(x) }, + y: unsafe { RetainedStyleValueData::from_retained_optional_pointer(y) }, })) }) } @@ -2032,28 +2256,28 @@ pub unsafe extern "C" fn rust_style_value_create_color_function( has_color_type: bool, color_type: u8, color_syntax: u8, - channel_0: *const c_void, - channel_1: *const c_void, - channel_2: *const c_void, - alpha: *const c_void, + channel_0: *const StyleValueData, + channel_1: *const StyleValueData, + channel_2: *const StyleValueData, + alpha: *const StyleValueData, has_name: bool, name: usize, - origin_color: *const c_void, -) -> *mut StyleValueData { + origin_color: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ColorFunction { + Arc::into_raw(Arc::new(StyleValueData::ColorFunction { color_base: ColorBase { has_color_type, color_type, color_syntax, }, - channel_0: RetainedStyleValue { pointer: channel_0 }, - channel_1: RetainedStyleValue { pointer: channel_1 }, - channel_2: RetainedStyleValue { pointer: channel_2 }, - alpha: RetainedStyleValue { pointer: alpha }, + channel_0: unsafe { RetainedStyleValueData::from_retained_pointer(channel_0) }, + channel_1: unsafe { RetainedStyleValueData::from_retained_pointer(channel_1) }, + channel_2: unsafe { RetainedStyleValueData::from_retained_pointer(channel_2) }, + alpha: unsafe { RetainedStyleValueData::from_retained_optional_pointer(alpha) }, has_name, name: RetainedUtf16FlyString { raw: name }, - origin_color: RetainedStyleValue { pointer: origin_color }, + origin_color: unsafe { RetainedStyleValueData::from_retained_optional_pointer(origin_color) }, })) }) } @@ -2064,30 +2288,26 @@ pub unsafe extern "C" fn rust_style_value_create_color_mix( has_color_type: bool, color_type: u8, color_syntax: u8, - color_interpolation_method: *const c_void, - first_color: *const c_void, - first_percentage: *const c_void, - second_color: *const c_void, - second_percentage: *const c_void, -) -> *mut StyleValueData { + color_interpolation_method: *const StyleValueData, + first_color: *const StyleValueData, + first_percentage: *const StyleValueData, + second_color: *const StyleValueData, + second_percentage: *const StyleValueData, +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ColorMix { + Arc::into_raw(Arc::new(StyleValueData::ColorMix { color_base: ColorBase { has_color_type, color_type, color_syntax, }, - color_interpolation_method: RetainedStyleValue { - pointer: color_interpolation_method, - }, - first_color: RetainedStyleValue { pointer: first_color }, - first_percentage: RetainedStyleValue { - pointer: first_percentage, - }, - second_color: RetainedStyleValue { pointer: second_color }, - second_percentage: RetainedStyleValue { - pointer: second_percentage, + color_interpolation_method: unsafe { + RetainedStyleValueData::from_retained_optional_pointer(color_interpolation_method) }, + first_color: unsafe { RetainedStyleValueData::from_retained_pointer(first_color) }, + first_percentage: unsafe { RetainedStyleValueData::from_retained_optional_pointer(first_percentage) }, + second_color: unsafe { RetainedStyleValueData::from_retained_pointer(second_color) }, + second_percentage: unsafe { RetainedStyleValueData::from_retained_optional_pointer(second_percentage) }, })) }) } @@ -2097,9 +2317,9 @@ pub unsafe extern "C" fn rust_style_value_create_color_mix( pub unsafe extern "C" fn rust_style_value_create_image_set( options: *const RetainedImageSetOption, length: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ImageSet { + Arc::into_raw(Arc::new(StyleValueData::ImageSet { options: unsafe { RetainedImageSetOptionList::from_raw(options, length) }, })) }) @@ -2110,27 +2330,25 @@ pub unsafe extern "C" fn rust_style_value_create_image_set( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_linear_gradient( has_direction_value: bool, - direction_value: *const c_void, + direction_value: *const StyleValueData, side_or_corner: u8, stops: *const RetainedColorStop, stop_count: usize, gradient_type: u8, repeating: bool, - color_interpolation_method: *const c_void, + color_interpolation_method: *const StyleValueData, color_syntax: u8, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::LinearGradient { + Arc::into_raw(Arc::new(StyleValueData::LinearGradient { has_direction_value, - direction_value: RetainedStyleValue { - pointer: direction_value, - }, + direction_value: unsafe { RetainedStyleValueData::from_retained_optional_pointer(direction_value) }, side_or_corner, color_stop_list: unsafe { RetainedColorStopList::from_raw(stops, stop_count) }, gradient_type, repeating, - color_interpolation_method: RetainedStyleValue { - pointer: color_interpolation_method, + color_interpolation_method: unsafe { + RetainedStyleValueData::from_retained_optional_pointer(color_interpolation_method) }, color_syntax, })) @@ -2141,22 +2359,22 @@ pub unsafe extern "C" fn rust_style_value_create_linear_gradient( /// values. #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_conic_gradient( - from_angle: *const c_void, - position: *const c_void, + from_angle: *const StyleValueData, + position: *const StyleValueData, stops: *const RetainedColorStop, stop_count: usize, repeating: bool, - color_interpolation_method: *const c_void, + color_interpolation_method: *const StyleValueData, color_syntax: u8, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::ConicGradient { - from_angle: RetainedStyleValue { pointer: from_angle }, - position: RetainedStyleValue { pointer: position }, + Arc::into_raw(Arc::new(StyleValueData::ConicGradient { + from_angle: unsafe { RetainedStyleValueData::from_retained_optional_pointer(from_angle) }, + position: unsafe { RetainedStyleValueData::from_retained_pointer(position) }, color_stop_list: unsafe { RetainedColorStopList::from_raw(stops, stop_count) }, repeating, - color_interpolation_method: RetainedStyleValue { - pointer: color_interpolation_method, + color_interpolation_method: unsafe { + RetainedStyleValueData::from_retained_optional_pointer(color_interpolation_method) }, color_syntax, })) @@ -2168,23 +2386,23 @@ pub unsafe extern "C" fn rust_style_value_create_conic_gradient( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_radial_gradient( ending_shape: u8, - size: *const c_void, - position: *const c_void, + size: *const StyleValueData, + position: *const StyleValueData, stops: *const RetainedColorStop, stop_count: usize, repeating: bool, - color_interpolation_method: *const c_void, + color_interpolation_method: *const StyleValueData, color_syntax: u8, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::RadialGradient { + Arc::into_raw(Arc::new(StyleValueData::RadialGradient { ending_shape, - size: RetainedStyleValue { pointer: size }, - position: RetainedStyleValue { pointer: position }, + size: unsafe { RetainedStyleValueData::from_retained_pointer(size) }, + position: unsafe { RetainedStyleValueData::from_retained_pointer(position) }, color_stop_list: unsafe { RetainedColorStopList::from_raw(stops, stop_count) }, repeating, - color_interpolation_method: RetainedStyleValue { - pointer: color_interpolation_method, + color_interpolation_method: unsafe { + RetainedStyleValueData::from_retained_optional_pointer(color_interpolation_method) }, color_syntax, })) @@ -2198,9 +2416,9 @@ pub unsafe extern "C" fn rust_style_value_create_grid_template_area( area_count: usize, row_count: usize, column_count: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::GridTemplateArea { + Arc::into_raw(Arc::new(StyleValueData::GridTemplateArea { grid_areas: unsafe { RetainedGridAreaList::from_raw(areas, area_count) }, row_count, column_count, @@ -2215,24 +2433,22 @@ pub unsafe extern "C" fn rust_style_value_create_easing( kind: u8, linear_stops: *const RetainedLinearEasingStop, linear_stop_count: usize, - x1: *const c_void, - y1: *const c_void, - x2: *const c_void, - y2: *const c_void, - number_of_intervals: *const c_void, + x1: *const StyleValueData, + y1: *const StyleValueData, + x2: *const StyleValueData, + y2: *const StyleValueData, + number_of_intervals: *const StyleValueData, step_position: u8, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Easing { + Arc::into_raw(Arc::new(StyleValueData::Easing { kind, linear_stops: unsafe { RetainedLinearEasingStopList::from_raw(linear_stops, linear_stop_count) }, - x1: RetainedStyleValue { pointer: x1 }, - y1: RetainedStyleValue { pointer: y1 }, - x2: RetainedStyleValue { pointer: x2 }, - y2: RetainedStyleValue { pointer: y2 }, - number_of_intervals: RetainedStyleValue { - pointer: number_of_intervals, - }, + x1: unsafe { RetainedStyleValueData::from_retained_optional_pointer(x1) }, + y1: unsafe { RetainedStyleValueData::from_retained_optional_pointer(y1) }, + x2: unsafe { RetainedStyleValueData::from_retained_optional_pointer(x2) }, + y2: unsafe { RetainedStyleValueData::from_retained_optional_pointer(y2) }, + number_of_intervals: unsafe { RetainedStyleValueData::from_retained_optional_pointer(number_of_intervals) }, step_position, })) }) @@ -2245,9 +2461,9 @@ pub unsafe extern "C" fn rust_style_value_create_grid_track_size_list( preserve_line_name_sets: bool, entries: *const GridTrackEntryInput, entry_count: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::GridTrackSizeList { + Arc::into_raw(Arc::new(StyleValueData::GridTrackSizeList { is_subgrid, preserve_line_name_sets, entries: unsafe { RetainedGridTrackEntryList::from_raw(entries, entry_count) }, @@ -2260,24 +2476,24 @@ pub unsafe extern "C" fn rust_style_value_create_grid_track_size_list( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_basic_shape( kind: u8, - v0: *const c_void, - v1: *const c_void, - v2: *const c_void, - v3: *const c_void, - v4: *const c_void, + v0: *const StyleValueData, + v1: *const StyleValueData, + v2: *const StyleValueData, + v3: *const StyleValueData, + v4: *const StyleValueData, fill_rule: u8, points: *const RetainedShapePoint, point_count: usize, path_string: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::BasicShape { + Arc::into_raw(Arc::new(StyleValueData::BasicShape { kind, - v0: RetainedStyleValue { pointer: v0 }, - v1: RetainedStyleValue { pointer: v1 }, - v2: RetainedStyleValue { pointer: v2 }, - v3: RetainedStyleValue { pointer: v3 }, - v4: RetainedStyleValue { pointer: v4 }, + v0: unsafe { RetainedStyleValueData::from_retained_optional_pointer(v0) }, + v1: unsafe { RetainedStyleValueData::from_retained_optional_pointer(v1) }, + v2: unsafe { RetainedStyleValueData::from_retained_optional_pointer(v2) }, + v3: unsafe { RetainedStyleValueData::from_retained_optional_pointer(v3) }, + v4: unsafe { RetainedStyleValueData::from_retained_optional_pointer(v4) }, fill_rule, points: unsafe { RetainedShapePointList::from_raw(points, point_count) }, path_string: RetainedUtf16FlyString { raw: path_string }, @@ -2290,8 +2506,7 @@ pub unsafe extern "C" fn rust_style_value_create_basic_shape( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_calculated( rust_calculation: *const crate::calc::CalcNode, - resolved_type: *const u8, - resolved_type_length: usize, + resolved_type: crate::calc::FfiNumericType, has_percentages_resolve_as: bool, resolve_as_is_number: bool, resolve_as_base: u8, @@ -2299,13 +2514,13 @@ pub unsafe extern "C" fn rust_style_value_create_calculated( resolve_numbers_as_integers: bool, accepted_ranges: *const RetainedNumericRangeByType, accepted_range_count: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Calculated { + Arc::into_raw(Arc::new(StyleValueData::Calculated { rust_calculation: unsafe { crate::calc::CalcNodeHandle::from_raw(rust_calculation) }, resolve_as_is_number, resolve_as_base, - resolved_type: unsafe { RetainedByteList::from_raw(resolved_type, resolved_type_length) }, + resolved_type, has_percentages_resolve_as, percentages_resolve_as, resolve_numbers_as_integers, @@ -2318,13 +2533,15 @@ pub unsafe extern "C" fn rust_style_value_create_calculated( #[unsafe(no_mangle)] pub unsafe extern "C" fn rust_style_value_create_image( url: usize, + url_bytes: *const u8, + url_length: usize, url_type: u8, url_modifiers: *const RetainedRequestUrlModifier, url_modifier_count: usize, -) -> *mut StyleValueData { +) -> *const StyleValueData { abort_on_panic(|| { - Box::into_raw(Box::new(StyleValueData::Image { - url: RetainedString { raw: url }, + Arc::into_raw(Arc::new(StyleValueData::Image { + url: unsafe { RetainedString::from_raw(url, url_bytes, url_length) }, url_type, url_modifiers: unsafe { RetainedRequestUrlModifierList::from_raw(url_modifiers, url_modifier_count) }, })) @@ -2332,53 +2549,56 @@ pub unsafe extern "C" fn rust_style_value_create_image( } #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_style_value_destroy(value: *mut StyleValueData) { +pub unsafe extern "C" fn rust_style_value_release(value: *const StyleValueData) { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StyleValueDestroyEntry); abort_on_panic(|| { if value.is_null() { return; } - drop(unsafe { Box::from_raw(value) }); + unsafe { Arc::decrement_strong_count(value) }; }); } +/// Retains one reference to a shared style value allocation. +/// +/// # Safety +/// `value` must be null or point at a live `StyleValueData` allocated by this module. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_style_value_retain(value: *const StyleValueData) -> *const StyleValueData { + abort_on_panic(|| { + if !value.is_null() { + unsafe { Arc::increment_strong_count(value) }; + } + value + }) +} + /// Whether a value's computed color depends on the element's used currentcolor: the /// currentcolor keyword itself, or a color function, color-mix(), contrast-color() or -/// light-dark() whose nested colors do. `data_of` maps a nested value's shell pointer to -/// its Rust-owned data. -pub(crate) fn value_depends_on_current_color( - value: &StyleValueData, - data_of: unsafe extern "C" fn(*const c_void) -> *const c_void, -) -> bool { - let retained_depends = |retained: &RetainedStyleValue| -> bool { - let shell = retained.shell_pointer(); - if shell.is_null() { - return false; - } - let data = unsafe { data_of(shell) }; - value_depends_on_current_color(unsafe { &*(data as *const StyleValueData) }, data_of) - }; +/// light-dark() whose nested colors do. +pub(crate) fn value_depends_on_current_color(value: &StyleValueData) -> bool { + let retained_data_depends = + |retained: &RetainedStyleValueData| -> bool { value_depends_on_current_color(retained.data()) }; match value { StyleValueData::Keyword { keyword } => *keyword == crate::style_compute::keyword::CURRENTCOLOR, - StyleValueData::ColorFunction { origin_color, .. } => retained_depends(origin_color), + StyleValueData::ColorFunction { origin_color, .. } => { + origin_color.optional_data().is_some_and(value_depends_on_current_color) + } StyleValueData::ColorMix { first_color, second_color, .. - } => retained_depends(first_color) || retained_depends(second_color), - StyleValueData::ContrastColor { color, .. } => retained_depends(color), - StyleValueData::LightDark { light, dark, .. } => retained_depends(light) || retained_depends(dark), + } => retained_data_depends(first_color) || retained_data_depends(second_color), + StyleValueData::ContrastColor { color, .. } => retained_data_depends(color), + StyleValueData::LightDark { light, dark, .. } => retained_data_depends(light) || retained_data_depends(dark), _ => false, } } /// # Safety -/// `data` must point at a valid StyleValueData and `data_of` must be a valid callback. +/// `data` must point at a valid StyleValueData. #[unsafe(no_mangle)] -pub unsafe extern "C" fn rust_style_value_depends_on_current_color( - data: *const c_void, - data_of: unsafe extern "C" fn(shell: *const c_void) -> *const c_void, -) -> bool { +pub unsafe extern "C" fn rust_style_value_depends_on_current_color(data: *const c_void) -> bool { crate::ffi_stats::bump(crate::ffi_stats::FfiOp::StyleValueQueryEntry); - crate::abort_on_panic(|| value_depends_on_current_color(unsafe { &*(data as *const StyleValueData) }, data_of)) + crate::abort_on_panic(|| value_depends_on_current_color(unsafe { &*(data as *const StyleValueData) })) } diff --git a/Libraries/LibWeb/CSS/Rust/src/transition.rs b/Libraries/LibWeb/CSS/Rust/src/transition.rs new file mode 100644 index 0000000000000..e64efdf2d8e89 --- /dev/null +++ b/Libraries/LibWeb/CSS/Rust/src/transition.rs @@ -0,0 +1,429 @@ +/* + * Copyright (c) 2026-present, the Ladybird developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +//! CSS transition decisions. + +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u8)] +pub enum FfiTransitionActionKind { + None, + Remove, + Cancel, + Start, + RemoveAndStart, + CancelRemoveAndStartReversing, + CancelRemoveAndStartInterrupted, +} + +#[repr(C)] +pub struct FfiTransitionPropertyInput { + pub property_id: u16, + pub before_change_value: *const crate::style_value::StyleValueData, + pub after_change_value: *const crate::style_value::StyleValueData, + pub current_value: *const crate::style_value::StyleValueData, + pub existing_end_value: *const crate::style_value::StyleValueData, + pub reversing_adjusted_start_value: *const crate::style_value::StyleValueData, + pub has_matching_transition: bool, + pub allow_discrete: bool, + pub before_change_value_originates_from_current_color: bool, + pub after_change_value_originates_from_current_color: bool, + pub has_running_transition: bool, + pub has_completed_transition: bool, + pub delay: f64, + pub duration: f64, + pub old_timing_function_output: f64, + pub old_reversing_shortening_factor: f64, +} + +#[repr(C)] +pub struct FfiTransitionInput { + pub context: crate::animation::FfiAnimationContext, + pub properties: *const FfiTransitionPropertyInput, + pub property_count: usize, +} + +#[repr(C)] +pub struct FfiTransitionAction { + pub property_id: u16, + pub kind: FfiTransitionActionKind, + pub delay: f64, + pub active_duration: f64, + pub reversing_shortening_factor: f64, +} + +fn property_values_are_transitionable( + context: &crate::animation::FfiAnimationContext, + property_id: u16, + old_value: *const crate::style_value::StyleValueData, + new_value: *const crate::style_value::StyleValueData, + allow_discrete: bool, +) -> bool { + let animation_type = crate::property_metadata::property_animation_type(property_id); + + // https://drafts.csswg.org/css-transitions/#transitionable + // When comparing the before-change style and after-change style for a given property, the property values are transitionable if they have an animation type that is neither not animatable nor discrete. + if animation_type == crate::animation::ANIMATION_TYPE_NONE + || !allow_discrete && animation_type == crate::animation::ANIMATION_TYPE_DISCRETE + { + return false; + } + if allow_discrete { + return true; + } + + let result = crate::animation::interpolate_value( + Some(context), + property_id, + unsafe { &*old_value }, + unsafe { &*new_value }, + 0.5, + ); + assert!(result.handled); + if !result.value.is_null() { + unsafe { crate::style_value::rust_style_value_release(result.value) }; + return true; + } + false +} + +fn decide_transition( + context: &crate::animation::FfiAnimationContext, + input: &FfiTransitionPropertyInput, +) -> FfiTransitionAction { + let values_equal = |first: *const crate::style_value::StyleValueData, + second: *const crate::style_value::StyleValueData| { + assert!(!first.is_null()); + assert!(!second.is_null()); + let (first, second) = unsafe { (&*first, &*second) }; + std::ptr::eq(first, second) || first == second + }; + let before_change_value_differs = input.has_matching_transition + && !(input.before_change_value_originates_from_current_color + && input.after_change_value_originates_from_current_color) + && !values_equal(input.before_change_value, input.after_change_value); + let existing_end_value_differs = input.has_matching_transition + && (input.has_running_transition || input.has_completed_transition) + && !values_equal(input.existing_end_value, input.after_change_value); + let current_value_equals_after = input.has_matching_transition + && input.has_running_transition + && values_equal(input.current_value, input.after_change_value); + let reversing_start_value_equals_after = input.has_matching_transition + && input.has_running_transition + && values_equal(input.reversing_adjusted_start_value, input.after_change_value); + + // https://drafts.csswg.org/css-transitions/#transition-combined-duration + // Define the combined duration of the transition as the sum of max(matching transition duration, 0s) and the matching transition delay. + let combined_duration = input.duration.max(0.0) + input.delay; + let before_after_transitionable = input.has_matching_transition + && before_change_value_differs + && property_values_are_transitionable( + context, + input.property_id, + input.before_change_value, + input.after_change_value, + input.allow_discrete, + ); + let current_after_transitionable = current_value_equals_after + || input.has_running_transition + && existing_end_value_differs + && property_values_are_transitionable( + context, + input.property_id, + input.current_value, + input.after_change_value, + input.allow_discrete, + ); + let mut action = FfiTransitionAction { + property_id: input.property_id, + kind: FfiTransitionActionKind::None, + delay: input.delay, + active_duration: input.duration, + reversing_shortening_factor: 1.0, + }; + + // https://drafts.csswg.org/css-transitions/#starting + // For each element and property, the implementation must act as follows: + + // 1. If all of the following are true: + // - the element does not have a running transition for the property, + // - there is a matching transition-property value, and + // - the before-change style is different from the after-change style for that property, and the values for the property are transitionable, + // - the element does not have a completed transition for the property or the end value of the completed transition is different from the + // after-change style for the property, + // - the combined duration is greater than 0s, + if !input.has_running_transition + && input.has_matching_transition + && before_change_value_differs + && before_after_transitionable + && (!input.has_completed_transition || existing_end_value_differs) + && combined_duration > 0.0 + { + // then implementations must remove the completed transition (if present) from the set of completed transitions + // and start a transition whose: + // - start time is the time of the style change event plus the matching transition delay, + // - end time is the start time plus the matching transition duration, + // - start value is the value of the transitioning property in the before-change style, + // - end value is the value of the transitioning property in the after-change style, + // - reversing-adjusted start value is the same as the start value, and + // - reversing shortening factor is 1. + action.kind = if input.has_completed_transition { + FfiTransitionActionKind::RemoveAndStart + } else { + FfiTransitionActionKind::Start + }; + return action; + } + + // 2. Otherwise, if the element has a completed transition for the property and the end value of the completed transition is different from the + // after-change style for the property, then implementations must remove the completed transition from the set of completed transitions. + if input.has_completed_transition && existing_end_value_differs { + action.kind = FfiTransitionActionKind::Remove; + return action; + } + + // 3. If the element has a running transition or completed transition for the property, and there is not a matching transition-property value, + // then implementations must cancel the running transition or remove the completed transition from the set of completed transitions. + if !input.has_matching_transition { + action.kind = if input.has_running_transition { + FfiTransitionActionKind::Cancel + } else if input.has_completed_transition { + FfiTransitionActionKind::Remove + } else { + FfiTransitionActionKind::None + }; + return action; + } + + // 4. If the element has a running transition for the property, there is a matching transition-property value, and the end value of the running + // transition is not equal to the value of the property in the after-change style, then: + if input.has_running_transition && existing_end_value_differs { + // 1. If the current value of the property in the running transition is equal to the value of the property in the after-change style, or if + // these two values are not transitionable, then implementations must cancel the running transition. + if current_value_equals_after || !current_after_transitionable { + action.kind = FfiTransitionActionKind::Cancel; + return action; + } + + // 2. Otherwise, if the combined duration is less than or equal to 0s, or if the current value of the property in the running transition is + // not transitionable with the value of the property in the after-change style, then implementations must cancel the running transition. + if combined_duration <= 0.0 || !current_after_transitionable { + action.kind = FfiTransitionActionKind::Cancel; + return action; + } + + // 3. Otherwise, if the reversing-adjusted start value of the running transition is the same as the value of the property in the after-change style + // (see the section on reversing of transitions for why these case exists), + if reversing_start_value_equals_after { + // implementations must cancel the running transition and start a new transition whose: + // - reversing-adjusted start value is the end value of the running transition, + // - reversing shortening factor is the absolute value, clamped to the range [0, 1], of the sum of: + // 1. the output of the timing function of the old transition at the time of the style change event, + // times the reversing shortening factor of the old transition + // 2. 1 minus the reversing shortening factor of the old transition. + let term_1 = input.old_timing_function_output * input.old_reversing_shortening_factor; + let term_2 = 1.0 - input.old_reversing_shortening_factor; + let reversing_shortening_factor = (term_1 + term_2).abs().clamp(0.0, 1.0); + action.kind = FfiTransitionActionKind::CancelRemoveAndStartReversing; + action.reversing_shortening_factor = reversing_shortening_factor; + action.delay = if input.delay >= 0.0 { + input.delay + } else { + reversing_shortening_factor * input.delay + }; + // - start time is the time of the style change event plus: + // 1. if the matching transition delay is nonnegative, the matching transition delay, or + // 2. if the matching transition delay is negative, the product of the new transition’s reversing shortening factor and the matching transition delay, + // - end time is the start time plus the product of the matching transition duration and the new transition’s reversing shortening factor, + // - start value is the current value of the property in the running transition, + // - end value is the value of the property in the after-change style, + action.active_duration = input.duration * reversing_shortening_factor; + return action; + } + + // 4. Otherwise, + // implementations must cancel the running transition and start a new transition whose: + // - start time is the time of the style change event plus the matching transition delay, + // - end time is the start time plus the matching transition duration, + // - start value is the current value of the property in the running transition, + // - end value is the value of the property in the after-change style, + // - reversing-adjusted start value is the same as the start value, and + // - reversing shortening factor is 1. + action.kind = FfiTransitionActionKind::CancelRemoveAndStartInterrupted; + } + + action +} + +/// Run the CSS Transitions decision algorithm for every supplied property. +/// +/// C++ retains ownership of animation objects and executes the returned actions in order. +/// +/// # Safety +/// `input` must point to a live value for the duration of the call. When the input contains +/// properties, `actions` must point at writable storage for `property_count` actions. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rust_decide_transitions(input: *const FfiTransitionInput, actions: *mut FfiTransitionAction) { + crate::abort_on_panic(|| { + crate::ffi_stats::rust_style_ffi_note_transition_decision(); + let input = unsafe { &*input }; + let properties = if input.property_count == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(input.properties, input.property_count) } + }; + for (index, property) in properties.iter().enumerate() { + unsafe { actions.add(index).write(decide_transition(&input.context, property)) }; + } + }); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn animation_context() -> crate::animation::FfiAnimationContext { + let font_metrics = || crate::animation::FfiAnimationFontMetrics { + font_size: 0.0, + x_height: 0.0, + cap_height: 0.0, + zero_advance: 0.0, + line_height: 0.0, + }; + crate::animation::FfiAnimationContext { + allow_discrete: false, + current_color: std::ptr::null(), + has_length_resolution_context: false, + length_resolution_context: crate::animation::FfiAnimationLengthResolutionContext { + viewport_width: 0.0, + viewport_height: 0.0, + font_metrics: font_metrics(), + root_font_metrics: font_metrics(), + font_metrics_depend_on_viewport_metrics: false, + root_font_metrics_depend_on_viewport_metrics: false, + }, + has_transform_reference_box: false, + transform_reference_box_width: 0.0, + transform_reference_box_height: 0.0, + } + } + + fn by_computed_value_property() -> u16 { + (crate::property_metadata::FIRST_LONGHAND_PROPERTY_ID..=crate::property_metadata::LAST_LONGHAND_PROPERTY_ID) + .find(|property_id| { + crate::property_metadata::property_animation_type(*property_id) + == crate::animation::ANIMATION_TYPE_BY_COMPUTED_VALUE + }) + .unwrap() + } + + fn input( + before_change_value: &crate::style_value::StyleValueData, + after_change_value: &crate::style_value::StyleValueData, + current_value: &crate::style_value::StyleValueData, + ) -> FfiTransitionPropertyInput { + FfiTransitionPropertyInput { + property_id: by_computed_value_property(), + before_change_value, + after_change_value, + current_value, + existing_end_value: std::ptr::null(), + reversing_adjusted_start_value: std::ptr::null(), + has_matching_transition: true, + allow_discrete: false, + before_change_value_originates_from_current_color: false, + after_change_value_originates_from_current_color: false, + has_running_transition: false, + has_completed_transition: false, + delay: 0.0, + duration: 100.0, + old_timing_function_output: 0.0, + old_reversing_shortening_factor: 1.0, + } + } + + #[test] + fn starts_an_initial_transition() { + let before = crate::style_value::StyleValueData::Number { value: 0.0 }; + let after = crate::style_value::StyleValueData::Number { value: 1.0 }; + assert_eq!( + decide_transition(&animation_context(), &input(&before, &after, &before)).kind, + FfiTransitionActionKind::Start + ); + } + + #[test] + fn accepts_an_empty_transition_batch() { + let input = FfiTransitionInput { + context: animation_context(), + properties: std::ptr::null(), + property_count: 0, + }; + unsafe { rust_decide_transitions(&raw const input, std::ptr::null_mut()) }; + } + + #[test] + fn equal_nested_values_do_not_start_a_transition() { + let nested_value = || { + let number = std::sync::Arc::into_raw(std::sync::Arc::new(crate::style_value::StyleValueData::Number { + value: 0.5, + })); + crate::style_value::StyleValueData::OpacityValue { + value: unsafe { crate::style_value::RetainedStyleValueData::from_retained_pointer(number) }, + } + }; + let before = nested_value(); + let after = nested_value(); + assert_eq!( + decide_transition(&animation_context(), &input(&before, &after, &before)).kind, + FfiTransitionActionKind::None + ); + } + + #[test] + fn current_color_origins_are_equivalent() { + let before = crate::style_value::StyleValueData::Number { value: 0.0 }; + let after = crate::style_value::StyleValueData::Number { value: 1.0 }; + let mut input = input(&before, &after, &before); + input.before_change_value_originates_from_current_color = true; + input.after_change_value_originates_from_current_color = true; + assert_eq!( + decide_transition(&animation_context(), &input).kind, + FfiTransitionActionKind::None + ); + } + + #[test] + fn removes_a_completed_transition_before_replacement() { + let before = crate::style_value::StyleValueData::Number { value: 0.0 }; + let after = crate::style_value::StyleValueData::Number { value: 1.0 }; + let mut input = input(&before, &after, &before); + input.has_completed_transition = true; + input.existing_end_value = &raw const before; + assert_eq!( + decide_transition(&animation_context(), &input).kind, + FfiTransitionActionKind::RemoveAndStart + ); + } + + #[test] + fn adjusts_a_reversing_transition() { + let before = crate::style_value::StyleValueData::Number { value: 0.0 }; + let after = crate::style_value::StyleValueData::Number { value: 1.0 }; + let current = crate::style_value::StyleValueData::Number { value: 0.5 }; + let mut input = input(&before, &after, ¤t); + input.has_running_transition = true; + input.existing_end_value = &raw const before; + input.reversing_adjusted_start_value = &raw const after; + input.delay = -20.0; + input.old_timing_function_output = 0.25; + input.old_reversing_shortening_factor = 0.5; + let action = decide_transition(&animation_context(), &input); + assert_eq!(action.kind, FfiTransitionActionKind::CancelRemoveAndStartReversing); + assert_eq!(action.reversing_shortening_factor, 0.625); + assert_eq!(action.delay, -12.5); + assert_eq!(action.active_duration, 62.5); + } +} diff --git a/Libraries/LibWeb/CSS/RustStyleBridge.cpp b/Libraries/LibWeb/CSS/RustStyleBridge.cpp index b397cd4afe9b6..0c061695fc61e 100644 --- a/Libraries/LibWeb/CSS/RustStyleBridge.cpp +++ b/Libraries/LibWeb/CSS/RustStyleBridge.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include @@ -59,9 +60,48 @@ u8 invoke_rust_property_metadata_requires_computation_level(u16 property_id) return ComputedValuesFFI::rust_property_metadata_requires_computation_level(property_id); } -ComputedValuesFFI::FfiShellAndData invoke_rust_style_metadata_initial_value(u16 property_id) +u8 invoke_rust_property_metadata_animation_type(u16 property_id) { - return ComputedValuesFFI::rust_style_metadata_initial_value(property_id); + return ComputedValuesFFI::rust_property_metadata_animation_type(property_id); +} + +ComputedValuesFFI::FfiPropertyNumericRange const* invoke_rust_property_metadata_numeric_ranges(u16 property_id, size_t* length) +{ + return ComputedValuesFFI::rust_property_metadata_numeric_ranges(property_id, length); +} + +bool invoke_rust_animation_property_is_preferred(u16 a, u16 b) +{ + return ComputedValuesFFI::rust_animation_property_is_preferred(a, b); +} + +bool invoke_cpp_animation_property_is_preferred(u16 a, u16 b) +{ + auto property_is_logical_alias_including_shorthands = [](PropertyID property_id) { + if (property_is_shorthand(property_id)) + return property_is_logical_alias(expanded_longhands_for_shorthand(property_id)[0]); + return property_is_logical_alias(property_id); + }; + auto property_a = static_cast(a); + auto property_b = static_cast(b); + if (property_is_shorthand(property_a) != property_is_shorthand(property_b)) + return !property_is_shorthand(property_a); + if (property_is_shorthand(property_a)) { + auto a_length = expanded_longhands_for_shorthand(property_a).size(); + auto b_length = expanded_longhands_for_shorthand(property_b).size(); + if (a_length != b_length) + return a_length < b_length; + } + auto a_is_logical_alias = property_is_logical_alias_including_shorthands(property_a); + auto b_is_logical_alias = property_is_logical_alias_including_shorthands(property_b); + if (a_is_logical_alias != b_is_logical_alias) + return !a_is_logical_alias; + return camel_case_string_from_property_id(property_a) < camel_case_string_from_property_id(property_b); +} + +StyleValueFFI::StyleValueData const* invoke_rust_style_metadata_initial_value(u16 property_id) +{ + return static_cast(ComputedValuesFFI::rust_style_metadata_initial_value(property_id)); } ComputedValuesFFI::FfiAbsolutizedLength invoke_rust_absolutize_length(double value, u8 unit, ComputedValuesFFI::FfiLengthResolutionContext const* context) diff --git a/Libraries/LibWeb/CSS/RustStyleBridge.h b/Libraries/LibWeb/CSS/RustStyleBridge.h index 5e1aebbee60fc..6086c421b7958 100644 --- a/Libraries/LibWeb/CSS/RustStyleBridge.h +++ b/Libraries/LibWeb/CSS/RustStyleBridge.h @@ -24,7 +24,11 @@ WEB_API u16 invoke_rust_map_physical_to_logical_alias(u16 property_id, u8 writin WEB_API bool invoke_rust_property_metadata_is_shorthand(u16 property_id); WEB_API u16 const* invoke_rust_property_metadata_longhands_for_shorthand(u16 property_id, size_t* length); WEB_API u8 invoke_rust_property_metadata_requires_computation_level(u16 property_id); -WEB_API ComputedValuesFFI::FfiShellAndData invoke_rust_style_metadata_initial_value(u16 property_id); +WEB_API u8 invoke_rust_property_metadata_animation_type(u16 property_id); +WEB_API ComputedValuesFFI::FfiPropertyNumericRange const* invoke_rust_property_metadata_numeric_ranges(u16 property_id, size_t* length); +WEB_API bool invoke_rust_animation_property_is_preferred(u16 a, u16 b); +WEB_API bool invoke_cpp_animation_property_is_preferred(u16 a, u16 b); +WEB_API StyleValueFFI::StyleValueData const* invoke_rust_style_metadata_initial_value(u16 property_id); WEB_API ComputedValuesFFI::FfiAbsolutizedLength invoke_rust_absolutize_length(double value, u8 unit, ComputedValuesFFI::FfiLengthResolutionContext const* context); WEB_API i32 rust_css_pixels_multiply(i32 left, i32 right); diff --git a/Libraries/LibWeb/CSS/Serialize.cpp b/Libraries/LibWeb/CSS/Serialize.cpp index 168255fc51154..8bec2387da27c 100644 --- a/Libraries/LibWeb/CSS/Serialize.cpp +++ b/Libraries/LibWeb/CSS/Serialize.cpp @@ -218,14 +218,6 @@ void serialize_a_url(Utf16StringBuilder& builder, Utf16View url) builder.append_ascii(')'); } -// NOTE: No spec currently exists for serializing a <'unicode-range'>. -void serialize_unicode_ranges(StringBuilder& builder, Vector const& unicode_ranges) -{ - serialize_a_comma_separated_list(builder, unicode_ranges, [](auto& builder, Gfx::UnicodeRange unicode_range) -> void { - return serialize_a_string(builder, unicode_range.to_string()); - }); -} - // https://drafts.csswg.org/cssom/#serialize-a-css-value void serialize_a_number(StringBuilder& builder, double value) { diff --git a/Libraries/LibWeb/CSS/Serialize.h b/Libraries/LibWeb/CSS/Serialize.h index 2b6cb75ff9dd1..fa2e7a682b4a0 100644 --- a/Libraries/LibWeb/CSS/Serialize.h +++ b/Libraries/LibWeb/CSS/Serialize.h @@ -29,7 +29,6 @@ void serialize_a_string(StringBuilder&, Utf16View string); void serialize_a_string(Utf16StringBuilder&, Utf16View string); WEB_API void serialize_a_url(StringBuilder&, Utf16View url); void serialize_a_url(Utf16StringBuilder&, Utf16View url); -void serialize_unicode_ranges(StringBuilder&, Vector const& unicode_ranges); WEB_API void serialize_a_number(StringBuilder&, double value); WEB_API void serialize_a_number(Utf16StringBuilder&, double value); diff --git a/Libraries/LibWeb/CSS/Size.cpp b/Libraries/LibWeb/CSS/Size.cpp deleted file mode 100644 index 711d0caa0fa75..0000000000000 --- a/Libraries/LibWeb/CSS/Size.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2022, Andreas Kling - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -namespace Web::CSS { - -Size::Size(Type type, Optional length_percentage) - : m_type(type) - , m_length_percentage(move(length_percentage)) -{ -} - -CSSPixels Size::to_px(CSSPixels reference_value) const -{ - if (!m_length_percentage.has_value()) - return 0; - return m_length_percentage->resolved(reference_value).absolute_length_to_px(); -} - -Size Size::make_auto() -{ - return Size { Type::Auto }; -} - -Size Size::make_px(CSSPixels px) -{ - return make_length(Length::make_px(px)); -} - -Size Size::make_length(Length length) -{ - return Size { Type::Length, move(length) }; -} - -Size Size::make_percentage(Percentage percentage) -{ - return Size { Type::Percentage, move(percentage) }; -} - -Size Size::make_calculated(NonnullRefPtr calculated) -{ - return Size { Type::Calculated, move(calculated) }; -} - -Size Size::make_length_percentage(LengthPercentage const& length_percentage) -{ - if (length_percentage.is_length()) - return make_length(length_percentage.length()); - if (length_percentage.is_percentage()) - return make_percentage(length_percentage.percentage()); - VERIFY(length_percentage.is_calculated()); - return make_calculated(length_percentage.calculated()); -} - -Size Size::make_min_content() -{ - return Size { Type::MinContent }; -} - -Size Size::make_max_content() -{ - return Size { Type::MaxContent }; -} - -Size Size::make_fit_content(LengthPercentage available_space) -{ - return Size { Type::FitContent, move(available_space) }; -} - -Size Size::make_fit_content() -{ - return Size { Type::FitContent }; -} - -Size Size::make_none() -{ - return Size { Type::None }; -} - -Size Size::from_style_value(NonnullRefPtr const& value) -{ - if (value->is_keyword()) { - switch (value->to_keyword()) { - case Keyword::Auto: - return Size::make_auto(); - case Keyword::FitContent: - return Size::make_fit_content(); - case Keyword::MinContent: - return Size::make_min_content(); - case Keyword::MaxContent: - return Size::make_max_content(); - case Keyword::None: - return Size::make_none(); - default: - VERIFY_NOT_REACHED(); - } - } - if (value->is_function() && value->as_function().name() == "fit-content"_utf16_fly_string) - return Size::make_fit_content(LengthPercentage::from_style_value(value->as_function().value())); - - if (value->is_calculated()) - return Size::make_calculated(value->as_calculated()); - - if (value->is_percentage()) - return Size::make_percentage(value->as_percentage().percentage()); - - if (value->is_length()) - return Size::make_length(value->as_length().length()); - - // FIXME: Support `anchor-size(..)` - if (value->is_anchor_size()) - return Size::make_none(); - - dbgln("FIXME: Unsupported size value: `{}`, treating as `auto`", value->to_string(SerializationMode::Normal)); - return Size::make_auto(); -} - -bool Size::contains_percentage() const -{ - switch (m_type) { - case Type::Auto: - case Type::MinContent: - case Type::MaxContent: - case Type::None: - return false; - case Type::FitContent: - return m_length_percentage.has_value() && m_length_percentage->contains_percentage(); - default: - return m_length_percentage->contains_percentage(); - } -} - -void Size::serialize(StringBuilder& builder, SerializationMode mode) const -{ - switch (m_type) { - case Type::Auto: - builder.append("auto"sv); - break; - case Type::Calculated: - case Type::Length: - case Type::Percentage: - m_length_percentage->serialize(builder, mode); - break; - case Type::MinContent: - builder.append("min-content"sv); - break; - case Type::MaxContent: - builder.append("max-content"sv); - break; - case Type::FitContent: - if (!m_length_percentage.has_value()) { - builder.append("fit-content"sv); - } else { - builder.append("fit-content("sv); - m_length_percentage->serialize(builder, mode); - builder.append(")"sv); - } - break; - case Type::None: - builder.append("none"sv); - break; - } -} - -String Size::to_string(SerializationMode mode) const -{ - StringBuilder builder; - serialize(builder, mode); - return MUST(builder.to_string()); -} - -} diff --git a/Libraries/LibWeb/CSS/Size.h b/Libraries/LibWeb/CSS/Size.h index cb7141ba5ac0b..4c9a330cac824 100644 --- a/Libraries/LibWeb/CSS/Size.h +++ b/Libraries/LibWeb/CSS/Size.h @@ -9,92 +9,215 @@ #include #include +#include +#include +#include namespace Web::CSS { -class Size { +class Size : public ComputedValuesFFI::ComputedSize { public: - enum class Type { - Auto, - Calculated, - Length, - Percentage, - MinContent, - MaxContent, - FitContent, - None, // NOTE: This is only valid for max-width and max-height. - }; - - static Size make_auto(); - static Size make_px(CSSPixels); - static Size make_length(Length); - static Size make_percentage(Percentage); - static Size make_calculated(NonnullRefPtr); - static Size make_length_percentage(LengthPercentage const&); - static Size make_min_content(); - static Size make_max_content(); - static Size make_fit_content(LengthPercentage available_space); - static Size make_fit_content(); - static Size make_none(); - - static Size from_style_value(NonnullRefPtr const&); - - bool is_auto() const { return m_type == Type::Auto; } - bool is_calculated() const { return m_type == Type::Calculated; } - bool is_length() const { return m_type == Type::Length; } - bool is_percentage() const { return m_type == Type::Percentage; } - bool is_min_content() const { return m_type == Type::MinContent; } - bool is_max_content() const { return m_type == Type::MaxContent; } - bool is_fit_content() const { return m_type == Type::FitContent; } - bool is_none() const { return m_type == Type::None; } - Type type() const { return m_type; } + using Type = ComputedValuesFFI::ComputedSizeKind; + + Size(Size const& other) + : ComputedSize { other.kind, { other.value.pointer ? StyleValueFFI::rust_style_value_retain(static_cast(other.value.pointer)) : nullptr } } + { + } + + Size(Size&& other) + : ComputedSize { other.kind, { exchange(other.value.pointer, nullptr) } } + { + } + + Size& operator=(Size other) + { + swap(kind, other.kind); + swap(value.pointer, other.value.pointer); + return *this; + } + + ~Size() + { + StyleValueFFI::rust_style_value_release(static_cast(value.pointer)); + } + + static Size make_auto() { return Size { Type::Auto }; } + static Size make_px(CSSPixels px) { return make_length(Length::make_px(px)); } + static Size make_length(Length length) { return Size { Type::Length, move(length) }; } + static Size make_percentage(Percentage percentage) { return Size { Type::Percentage, move(percentage) }; } + static Size make_calculated(NonnullRefPtr calculated) { return Size { Type::Calculated, move(calculated) }; } + static Size make_min_content() { return Size { Type::MinContent }; } + static Size make_max_content() { return Size { Type::MaxContent }; } + static Size make_fit_content(LengthPercentage available_space) { return Size { Type::FitContent, move(available_space) }; } + static Size make_fit_content() { return Size { Type::FitContent }; } + static Size make_none() { return Size { Type::None }; } + + static Size from_style_value(NonnullRefPtr const& value) + { + if (value->is_keyword()) { + switch (value->to_keyword()) { + case Keyword::Auto: + return make_auto(); + case Keyword::FitContent: + return make_fit_content(); + case Keyword::MinContent: + return make_min_content(); + case Keyword::MaxContent: + return make_max_content(); + case Keyword::None: + return make_none(); + default: + VERIFY_NOT_REACHED(); + } + } + if (value->is_function() && value->as_function().name() == "fit-content"_utf16_fly_string) + return make_fit_content(LengthPercentage::from_style_value(value->as_function().value())); + if (value->is_calculated()) + return make_calculated(value->as_calculated()); + if (value->is_percentage()) + return make_percentage(value->as_percentage().percentage()); + if (value->is_length()) + return make_length(value->as_length().length()); + + // FIXME: Support `anchor-size(..)` + if (value->is_anchor_size()) + return make_none(); + + dbgln("FIXME: Unsupported size value: `{}`, treating as `auto`", value->to_string(SerializationMode::Normal)); + return make_auto(); + } + + bool is_auto() const { return kind == Type::Auto; } + bool is_calculated() const { return kind == Type::Calculated; } + bool is_length() const { return kind == Type::Length; } + bool is_percentage() const { return kind == Type::Percentage; } + bool is_min_content() const { return kind == Type::MinContent; } + bool is_max_content() const { return kind == Type::MaxContent; } + bool is_fit_content() const { return kind == Type::FitContent; } + bool is_none() const { return kind == Type::None; } + Type type() const { return kind; } bool is_intrinsic_sizing_constraint() const { return is_min_content() || is_max_content() || is_fit_content(); } bool is_length_percentage() const { return is_length() || is_percentage() || is_calculated(); } - [[nodiscard]] CSSPixels to_px(CSSPixels reference_value) const; + [[nodiscard]] CSSPixels to_px(CSSPixels reference_value) const + { + if (!value.pointer) + return 0; + return length_percentage().resolved(reference_value).absolute_length_to_px(); + } - bool contains_percentage() const; + bool contains_percentage() const + { + switch (kind) { + case Type::Auto: + case Type::MinContent: + case Type::MaxContent: + case Type::None: + return false; + case Type::FitContent: + return value.pointer && length_percentage().contains_percentage(); + default: + return length_percentage().contains_percentage(); + } + } - CalculatedStyleValue const& calculated() const + ValueComparingNonnullRefPtr calculated() const { VERIFY(is_calculated()); - return m_length_percentage->calculated(); + return length_percentage().calculated(); } - Length const& length() const + Length length() const { VERIFY(is_length()); - return m_length_percentage->length(); + return length_percentage().length(); } - Percentage const& percentage() const + Percentage percentage() const { VERIFY(is_percentage()); - return m_length_percentage->percentage(); + return length_percentage().percentage(); } LengthPercentage const& length_percentage() const { - VERIFY(is_length_percentage()); - return *m_length_percentage; + VERIFY(value.pointer); + return LengthPercentage::view(value); } - Optional const& fit_content_available_space() const + Optional fit_content_available_space() const { VERIFY(is_fit_content()); - return m_length_percentage; + if (!value.pointer) + return {}; + return length_percentage(); } - void serialize(StringBuilder&, SerializationMode) const; - String to_string(SerializationMode) const; - bool operator==(Size const&) const = default; + void serialize(StringBuilder& builder, SerializationMode mode) const + { + switch (kind) { + case Type::Auto: + builder.append("auto"sv); + break; + case Type::Calculated: + case Type::Length: + case Type::Percentage: + length_percentage().serialize(builder, mode); + break; + case Type::MinContent: + builder.append("min-content"sv); + break; + case Type::MaxContent: + builder.append("max-content"sv); + break; + case Type::FitContent: + if (!value.pointer) { + builder.append("fit-content"sv); + } else { + builder.append("fit-content("sv); + length_percentage().serialize(builder, mode); + builder.append(")"sv); + } + break; + case Type::None: + builder.append("none"sv); + break; + } + } -private: - explicit Size(Type type, Optional = {}); + String to_string(SerializationMode mode) const + { + StringBuilder builder; + serialize(builder, mode); + return MUST(builder.to_string()); + } + bool operator==(Size const& other) const + { + if (kind != other.kind) + return false; + if (!value.pointer || !other.value.pointer) + return value.pointer == other.value.pointer; + return length_percentage() == other.length_percentage(); + } - Type m_type {}; - Optional m_length_percentage; + static Size const& view(ComputedValuesFFI::ComputedSize const& size) + { + static_assert(sizeof(Size) == sizeof(size)); + return reinterpret_cast(size); + } + + static void replace(ComputedValuesFFI::ComputedSize& target, Size replacement) + { + swap(target.kind, replacement.kind); + swap(target.value.pointer, replacement.value.pointer); + } + +private: + explicit Size(Type type, Optional length_percentage = {}) + : ComputedSize { type, { length_percentage.has_value() ? length_percentage->leak_data() : nullptr } } + { + } }; } diff --git a/Libraries/LibWeb/CSS/StyleComputer.cpp b/Libraries/LibWeb/CSS/StyleComputer.cpp index f0abea07b0d09..5f9771c992d87 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -46,13 +46,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include #include #include @@ -76,7 +76,6 @@ #include #include #include -#include #include #include #include @@ -104,6 +103,7 @@ #include #include #include +#include #include namespace Web::CSS { @@ -848,31 +848,24 @@ static void sort_matching_rules(Vector& match void StyleComputer::for_each_property_expanding_shorthands(PropertyID property_id, StyleValue const& value, Function const& set_longhand_property) { - // The expansion recursion lives in the Rust style computation core; this wrapper provides - // the shell-level callbacks and pins every pending-substitution value it creates until the - // expansion returns. + // The expansion recursion and pending-substitution values live in the Rust style value graph. + // This wrapper creates C++ facades only for the longhand roots requested by the callback. struct ExpansionContext { Function const& set_longhand_property; - Vector> pinned_values; + HashMap> wrapper_cache; } expansion_context { set_longhand_property, {} }; ComputedValuesFFI::FfiShorthandExpansionCallbacks const callbacks { .context = &expansion_context, - .data_of = [](void*, void const* shell) -> void const* { - return static_cast(shell)->rust_style_value_data(); - }, - .create_pending_substitution = [](void* context, void const* shell) -> void const* { - auto& expansion_context = *static_cast(context); - auto pending_substitution_value = PendingSubstitutionStyleValue::create(*static_cast(shell)); - auto const* pointer = pending_substitution_value.ptr(); - expansion_context.pinned_values.append(move(pending_substitution_value)); - return pointer; - }, - .set_longhand_property = [](void* context, u16 property_id, void const* shell) { + .set_longhand_property = [](void* context, u16 property_id, void const* data) { auto& expansion_context = *static_cast(context); - expansion_context.set_longhand_property(static_cast(property_id), *static_cast(shell)); }, + auto& value = expansion_context.wrapper_cache.ensure(data, [&] { + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(data))); + }); + expansion_context.set_longhand_property(static_cast(property_id), *value); }, }; - ComputedValuesFFI::rust_for_each_property_expanding_shorthands(&callbacks, to_underlying(property_id), &value, value.rust_style_value_data()); + ComputedValuesFFI::rust_for_each_property_expanding_shorthands(&callbacks, to_underlying(property_id), value.rust_style_value_data()); } static RefPtr inheritable_custom_property_data(DOM::AbstractElement abstract_element) @@ -904,335 +897,492 @@ static Optional resolve_keyframe_easing(CSS::StyleValue con void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element, GC::Ref effect, ComputedProperties::Builder& builder) const { - collect_animation_into(abstract_element, effect, builder.style(), &builder); + Array effects { effect }; + collect_animations_into(abstract_element, effects.span(), builder); } void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element, GC::Ref effect, ComputedProperties& computed_properties) const { - collect_animation_into(abstract_element, effect, computed_properties, nullptr); + Array effects { effect }; + collect_animations_into(abstract_element, effects.span(), computed_properties); } -void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element, GC::Ref effect, ComputedProperties& computed_properties, ComputedProperties::Builder* builder) const +void StyleComputer::collect_animations_into(DOM::AbstractElement abstract_element, ReadonlySpan> effects, ComputedProperties::Builder& builder) const { - auto animation = effect->associated_animation(); - if (!animation) - return; - - auto output_progress = effect->transformed_progress(); - if (!output_progress.has_value()) - return; - - if (!effect->key_frame_set()) - return; - - auto& keyframes = effect->key_frame_set()->keyframes_by_key; - if (keyframes.size() < 2) { - if constexpr (LIBWEB_CSS_ANIMATION_DEBUG) { - dbgln(" Did not find enough keyframes ({} keyframes)", keyframes.size()); - for (auto it = keyframes.begin(); it != keyframes.end(); ++it) - dbgln(" - {}", it.key()); - } - return; - } + collect_animation_effects_into(abstract_element, effects, builder.style(), &builder); +} - double current_key = output_progress.value() * 100.0 * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor; - current_key = clamp(current_key, static_cast(NumericLimits::min()), static_cast(NumericLimits::max())); +void StyleComputer::collect_animations_into(DOM::AbstractElement abstract_element, ReadonlySpan> effects, ComputedProperties& computed_properties) const +{ + collect_animation_effects_into(abstract_element, effects, computed_properties, nullptr); +} - // Each property is animated using its property-specific keyframes, so two properties in the same animation may be - // interpolated across different intervals. - // Collect the keyframes in ascending offset order, and index for each physical longhand the keyframes that specify - // it, so that the interval endpoints can be found separately for every property. - LogicalAliasMappingContext const logical_alias_mapping_context { computed_properties.writing_mode(), computed_properties.direction() }; - struct KeyframeInfo { +void StyleComputer::collect_animation_effects_into(DOM::AbstractElement abstract_element, ReadonlySpan> effects, ComputedProperties& computed_properties, ComputedProperties::Builder* builder) const +{ + struct PreparedKeyframeValue { i64 key { 0 }; - Animations::KeyframeEffect::KeyFrameSet::ResolvedKeyFrame const* frame { nullptr }; + RefPtr value; + CSS::EasingFunction easing; + Bindings::CompositeOperation composite_operation; }; - Vector ordered_keyframes; - ordered_keyframes.ensure_capacity(keyframes.size()); - HashMap> keyframes_specifying_property; - for (auto it = keyframes.begin(); it != keyframes.end(); ++it) { - auto keyframe_index = ordered_keyframes.size(); - auto add_physical_longhand = [&](PropertyID longhand_id) { - auto physical_longhand_id = map_logical_alias_to_physical_property(longhand_id, logical_alias_mapping_context); - auto& specifying_keyframes = keyframes_specifying_property.ensure(physical_longhand_id); - if (specifying_keyframes.is_empty() || specifying_keyframes.last() != keyframe_index) - specifying_keyframes.append(keyframe_index); - }; - for (auto const& [property_id, value] : it->properties) { - value.visit( - [&](Animations::KeyframeEffect::KeyFrameSet::UseInitial) { add_physical_longhand(property_id); }, - [&](NonnullRefPtr const& keyframe_value) { - for_each_property_expanding_shorthands(property_id, *keyframe_value, [&](PropertyID longhand_id, StyleValue const&) { - add_physical_longhand(longhand_id); - }); + struct PreparedAnimationValue { + GC::Ref effect; + PropertyID property_id; + AnimatedPropertyResultOfTransition is_result_of_transition; + NonnullRefPtr underlying; + NonnullRefPtr initial; + double current_key { 0 }; + Vector keyframes; + }; + Vector prepared_values; + + struct KeyframeDeclaration { + size_t keyframe_index { 0 }; + PropertyID property_id; + NonnullRefPtr value; + bool use_initial { false }; + bool is_transition { false }; + }; + Vector keyframe_declarations; + Vector keyframes_by_index; + for (auto effect : effects) { + auto animation = effect->associated_animation(); + if (!animation || !effect->transformed_progress().has_value() || !effect->key_frame_set()) + continue; + auto& keyframes = effect->key_frame_set()->keyframes_by_key; + if (keyframes.size() < 2) + continue; + for (auto it = keyframes.begin(); it != keyframes.end(); ++it) { + auto keyframe_index = keyframes_by_index.size(); + keyframes_by_index.append(&*it); + for (auto const& [property_id, value] : it->properties) { + bool is_use_initial = false; + auto style_value = value.visit( + [&](Animations::KeyframeEffect::KeyFrameSet::UseInitial) -> RefPtr { + if (property_is_shorthand(property_id)) + return {}; + is_use_initial = true; + return computed_properties.property(property_id, ComputedProperties::WithAnimationsApplied::No); + }, + [](RefPtr value) -> RefPtr { return value; }); + if (!style_value || style_value->is_pending_substitution()) + continue; + if (style_value->is_unresolved()) + style_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { abstract_element.document() }, abstract_element, {}, PropertyNameAndID::from_id(property_id), style_value->as_unresolved()); + // https://drafts.csswg.org/css-values-5/#invalid-at-computed-value-time + // When substitution results in a guaranteed-invalid value, treat it as unset + // (i.e. inherit for inherited properties, initial for non-inherited properties). + if (!style_value || style_value->is_guaranteed_invalid()) + continue; + keyframe_declarations.append({ + .keyframe_index = keyframe_index, + .property_id = property_id, + .value = style_value.release_nonnull(), + .use_initial = is_use_initial, + .is_transition = animation->is_css_transition(), }); + } } - ordered_keyframes.append({ static_cast(it.key()), &*it }); } - // https://drafts.csswg.org/css-animations-1/#animation-timing-function - // Apply the per-keyframe easing to the interval progress. The easing on a keyframe applies to the - // interval from that keyframe to the next. If the keyframe doesn't specify an easing, use the - // animation's default easing (from the animation-timing-function property). - auto apply_keyframe_easing = [&](auto const& keyframe_easing, double interval_progress) { - auto resolved_easing = keyframe_easing.visit( - [](Empty) -> Optional { return {}; }, - [](CSS::EasingFunction const& easing) -> Optional { return easing; }, - [&](NonnullRefPtr const& value) -> Optional { - return resolve_keyframe_easing(*value, abstract_element); - }); - if (resolved_easing.has_value()) - return resolved_easing->evaluate_at(interval_progress, false); - if (animation->is_css_animation()) - return static_cast(*animation).default_easing().evaluate_at(interval_progress, false); - return interval_progress; + struct SelectedKeyframeValue { + PropertyID source_longhand_id; + NonnullRefPtr value; + StyleValueFFI::FfiAnimationSpecifiedValueSource value_source; }; + if (keyframe_declarations.is_empty()) + return; - // FIXME: Follow https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes in whatever the right place is. - auto compute_keyframe_values = [&computed_properties, &abstract_element, builder, this](auto const& keyframe_values) { - HashMap> result; - HashMap longhands_set_by_property_id; - AK::FixedBitmap property_is_set_by_use_initial(false); - - auto property_is_logical_alias_including_shorthands = [&](PropertyID property_id) { - if (property_is_shorthand(property_id)) - // NOTE: All expanded longhands for a logical alias shorthand are logical aliases so we only need to check the first one. - return property_is_logical_alias(expanded_longhands_for_shorthand(property_id)[0]); - - return property_is_logical_alias(property_id); - }; - - // https://drafts.csswg.org/web-animations-1/#ref-for-computed-keyframes - auto is_property_preferred = [&](PropertyID a, PropertyID b) { - // If conflicts arise when expanding shorthand properties or replacing logical properties with physical properties, apply the following rules in order until the conflict is resolved: - // 1. Longhand properties override shorthand properties (e.g. border-top-color overrides border-top). - if (property_is_shorthand(a) != property_is_shorthand(b)) - return !property_is_shorthand(a); - - // 2. Shorthand properties with fewer longhand components override those with more longhand components (e.g. border-top overrides border-color). - if (property_is_shorthand(a)) { - auto number_of_expanded_shorthands_a = expanded_longhands_for_shorthand(a).size(); - auto number_of_expanded_shorthands_b = expanded_longhands_for_shorthand(b).size(); - - if (number_of_expanded_shorthands_a != number_of_expanded_shorthands_b) - return number_of_expanded_shorthands_a < number_of_expanded_shorthands_b; - } - - auto property_a_is_logical_alias = property_is_logical_alias_including_shorthands(a); - auto property_b_is_logical_alias = property_is_logical_alias_including_shorthands(b); - - // 3. Physical properties override logical properties. - if (property_a_is_logical_alias != property_b_is_logical_alias) - return !property_a_is_logical_alias; - - // 4. For shorthand properties with an equal number of longhand components, properties whose IDL name (see - // the CSS property to IDL attribute algorithm [CSSOM]) appears earlier when sorted in ascending order - // by the Unicode codepoints that make up each IDL name, override those who appear later. - return camel_case_string_from_property_id(a) < camel_case_string_from_property_id(b); - }; - - HashMap> specified_values; - - for (auto const& [property_id, value] : keyframe_values.properties) { - bool is_use_initial = false; + Vector ffi_declarations; + ffi_declarations.ensure_capacity(keyframe_declarations.size()); + for (auto const& declaration : keyframe_declarations) { + ffi_declarations.unchecked_append({ + .keyframe_index = declaration.keyframe_index, + .property_id = to_underlying(declaration.property_id), + .value = declaration.value->rust_style_value_data(), + .use_initial = declaration.use_initial, + .is_transition = declaration.is_transition, + }); + } + Vector important_property_bitmap; + important_property_bitmap.resize((number_of_longhand_properties + 7) / 8); + for (size_t index = 0; index < number_of_longhand_properties; ++index) { + auto property_id = static_cast(to_underlying(first_longhand_property_id) + index); + if (computed_properties.is_property_important(property_id)) + important_property_bitmap[index / 8] |= 1 << (index % 8); + } - auto style_value = value.visit( - [&](Animations::KeyframeEffect::KeyFrameSet::UseInitial) -> RefPtr { - if (property_is_shorthand(property_id)) - return {}; - is_use_initial = true; - return computed_properties.property(property_id, ComputedProperties::WithAnimationsApplied::No); - }, - [&](RefPtr value) -> RefPtr { - return value; - }); + Vector ffi_values; + Vector ffi_results; + Vector> ffi_keyframes; + Vector>> linear_easing_points; + auto compute_animation_values = [&](ReadonlySpan resolved_properties) -> StyleValueFFI::FfiComputedAnimationBatch { + HashMap> selected_keyframe_values; + for (auto const& property : resolved_properties) { + VERIFY(property.keyframe_index < keyframes_by_index.size()); + auto* keyframe = keyframes_by_index[property.keyframe_index]; + auto& keyframe_values = selected_keyframe_values.ensure(keyframe); + keyframe_values.set(static_cast(property.physical_property_id), { + .source_longhand_id = static_cast(property.source_longhand_id), + .value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(property.value)), + .value_source = property.value_source, + }); + } - if (!style_value) { - specified_values.set(property_id, nullptr); + VERIFY(computation_context_cache_is_empty()); + for (auto effect : effects) { + auto animation = effect->associated_animation(); + if (!animation) continue; - } - // If the style value is a PendingSubstitutionStyleValue we should skip it to avoid overwriting any value - // already set by resolving the relevant shorthand's value. - if (style_value->is_pending_substitution()) + auto output_progress = effect->transformed_progress(); + if (!output_progress.has_value()) continue; - if (style_value->is_unresolved()) - style_value = Parser::Parser::resolve_unresolved_style_value(Parser::ParsingParams { abstract_element.document() }, abstract_element, {}, PropertyNameAndID::from_id(property_id), style_value->as_unresolved()); + if (!effect->key_frame_set()) + continue; - // https://drafts.csswg.org/css-values-5/#invalid-at-computed-value-time - // When substitution results in a guaranteed-invalid value, treat it as unset - // (i.e. inherit for inherited properties, initial for non-inherited properties). - if (style_value->is_guaranteed_invalid()) { - specified_values.set(property_id, nullptr); + auto& keyframes = effect->key_frame_set()->keyframes_by_key; + if (keyframes.size() < 2) { + if constexpr (LIBWEB_CSS_ANIMATION_DEBUG) { + dbgln(" Did not find enough keyframes ({} keyframes)", keyframes.size()); + for (auto it = keyframes.begin(); it != keyframes.end(); ++it) + dbgln(" - {}", it.key()); + } continue; } - for_each_property_expanding_shorthands(property_id, *style_value, [&](PropertyID longhand_id, StyleValue const& longhand_value) { - auto physical_longhand_id = map_logical_alias_to_physical_property(longhand_id, LogicalAliasMappingContext { computed_properties.writing_mode(), computed_properties.direction() }); - auto physical_longhand_id_bitmap_index = to_underlying(physical_longhand_id) - to_underlying(first_longhand_property_id); + double current_key = output_progress.value() * 100.0 * Animations::KeyframeEffect::AnimationKeyFrameKeyScaleFactor; + current_key = clamp(current_key, static_cast(NumericLimits::min()), static_cast(NumericLimits::max())); - // Don't overwrite values if this is the result of a UseInitial - if (specified_values.contains(physical_longhand_id) && specified_values.get(physical_longhand_id) != nullptr && is_use_initial) - return; - - // Don't overwrite unless the value was originally set by a UseInitial or this property is preferred over the one that set it originally - if (specified_values.contains(physical_longhand_id) && specified_values.get(physical_longhand_id) != nullptr && !property_is_set_by_use_initial.get(physical_longhand_id_bitmap_index) && !is_property_preferred(property_id, longhands_set_by_property_id.get(physical_longhand_id).value())) - return; - - auto specified_value_with_css_wide_keywords_applied = [&]() -> NonnullRefPtr { - if (longhand_value.is_inherit() || (longhand_value.is_unset() && is_inherited_property(longhand_id))) { - if (auto inherited_animated_value = get_animated_inherit_value(longhand_id, abstract_element); inherited_animated_value.has_value()) - return inherited_animated_value->value; - - return get_non_animated_inherit_value(longhand_id, abstract_element); + // Each property is animated using its property-specific keyframes, so two properties in the same animation may be + // interpolated across different intervals. + // Collect the keyframes in ascending offset order, and index for each physical longhand the keyframes that specify + // it, so that the interval endpoints can be found separately for every property. + struct KeyframeInfo { + i64 key { 0 }; + Animations::KeyframeEffect::KeyFrameSet::ResolvedKeyFrame const* frame { nullptr }; + }; + Vector ordered_keyframes; + ordered_keyframes.ensure_capacity(keyframes.size()); + HashMap> keyframes_specifying_property; + for (auto it = keyframes.begin(); it != keyframes.end(); ++it) { + auto keyframe_index = ordered_keyframes.size(); + if (auto selected_values = selected_keyframe_values.get(&*it); selected_values.has_value()) { + for (auto const& [physical_property_id, _] : *selected_values) { + auto& specifying_keyframes = keyframes_specifying_property.ensure(physical_property_id); + specifying_keyframes.append(keyframe_index); } + } + ordered_keyframes.append({ static_cast(it.key()), &*it }); + } - if (longhand_value.is_initial() || longhand_value.is_unset()) - return property_initial_value(longhand_id); - - if (longhand_value.is_revert() || longhand_value.is_revert_layer()) - return computed_properties.property(longhand_id); - - return NonnullRefPtr { longhand_value }; - }(); - - longhands_set_by_property_id.set(physical_longhand_id, property_id); - property_is_set_by_use_initial.set(physical_longhand_id_bitmap_index, is_use_initial); - specified_values.set(physical_longhand_id, specified_value_with_css_wide_keywords_applied); - }); - } + // https://drafts.csswg.org/css-animations-1/#animation-timing-function + // Apply the per-keyframe easing to the interval progress. The easing on a keyframe applies to the + // interval from that keyframe to the next. If the keyframe doesn't specify an easing, use the + // animation's default easing (from the animation-timing-function property). + auto resolve_interval_easing = [&](auto const& keyframe_easing) { + auto resolved_easing = keyframe_easing.visit( + [](Empty) -> Optional { return {}; }, + [](CSS::EasingFunction const& easing) -> Optional { return easing; }, + [&](NonnullRefPtr const& value) -> Optional { + return resolve_keyframe_easing(*value, abstract_element); + }); + if (resolved_easing.has_value()) + return resolved_easing.release_value(); + if (animation->is_css_animation()) + return static_cast(*animation).default_easing(); + return CSS::EasingFunction::linear(); + }; - // NOTE: This doesn't necessarily return the specified value if we reach into computed_properties but that - // doesn't matter as a computed value is always valid as a specified value. - Function(PropertyID)> get_property_specified_value = [&](PropertyID property_id) -> NonnullRefPtr { - if (auto keyframe_value = specified_values.get(property_id); keyframe_value.has_value() && keyframe_value.value()) - return *keyframe_value.value(); + auto compute_keyframe_values = [&computed_properties, &abstract_element, &selected_keyframe_values, builder, this](auto const& keyframe_values) { + HashMap> result; + HashMap> specified_values; + if (auto selected_values = selected_keyframe_values.get(&keyframe_values); selected_values.has_value()) { + for (auto const& [physical_property_id, selected_value] : *selected_values) { + auto longhand_id = selected_value.source_longhand_id; + auto specified_value = [&]() -> NonnullRefPtr { + switch (selected_value.value_source) { + case StyleValueFFI::FfiAnimationSpecifiedValueSource::Inherited: + if (auto inherited_animated_value = get_animated_inherit_value(longhand_id, abstract_element); inherited_animated_value.has_value()) + return inherited_animated_value->value; + return get_non_animated_inherit_value(longhand_id, abstract_element); + case StyleValueFFI::FfiAnimationSpecifiedValueSource::Initial: + return property_initial_value(longhand_id); + case StyleValueFFI::FfiAnimationSpecifiedValueSource::Underlying: + return computed_properties.property(longhand_id); + case StyleValueFFI::FfiAnimationSpecifiedValueSource::Value: + return selected_value.value; + } + VERIFY_NOT_REACHED(); + }(); + specified_values.set(physical_property_id, specified_value); + } + } - return computed_properties.property(property_id); - }; + // NOTE: This doesn't necessarily return the specified value if we reach into computed_properties but that + // doesn't matter as a computed value is always valid as a specified value. + Function(PropertyID)> get_property_specified_value = [&](PropertyID property_id) -> NonnullRefPtr { + if (auto keyframe_value = specified_values.get(property_id); keyframe_value.has_value() && keyframe_value.value()) + return *keyframe_value.value(); - for (auto const& [property_id, style_value] : specified_values) { - if (!style_value) - continue; + return computed_properties.property(property_id); + }; - auto const& computation_context = get_computation_context_for_property(property_id, computed_properties, abstract_element); + for (auto const& [property_id, style_value] : specified_values) { + if (!style_value) + continue; - computation_context.reset_viewport_metric_dependency_tracking(); - result.set(property_id, compute_value_of_property(property_id, *style_value, get_property_specified_value, computation_context, m_document->page().client().device_pixels_per_css_pixel())); - if (computation_context.depends_on_viewport_metrics()) { - if (builder) { - builder->set_depends_on_viewport_metrics(); - if (property_affects_font_metrics(property_id)) - builder->set_font_metrics_depend_on_viewport_metrics(); - } else { - computed_properties.set_depends_on_viewport_metrics(Badge {}); - if (property_affects_font_metrics(property_id)) - computed_properties.set_font_metrics_depend_on_viewport_metrics(Badge {}); + auto const& computation_context = get_computation_context_for_property(property_id, computed_properties, abstract_element); + + computation_context.reset_viewport_metric_dependency_tracking(); + result.set(property_id, compute_value_of_property(property_id, *style_value, get_property_specified_value, computation_context, m_document->page().client().device_pixels_per_css_pixel())); + if (computation_context.depends_on_viewport_metrics()) { + if (builder) { + builder->set_depends_on_viewport_metrics(); + if (property_affects_font_metrics(property_id)) + builder->set_font_metrics_depend_on_viewport_metrics(); + } else { + computed_properties.set_depends_on_viewport_metrics(Badge {}); + if (property_affects_font_metrics(property_id)) + computed_properties.set_font_metrics_depend_on_viewport_metrics(Badge {}); + } + } } - } - } - return result; - }; + return result; + }; - auto to_composite_operation = [&](Bindings::CompositeOperationOrAuto composite_operation_or_auto) { - switch (composite_operation_or_auto) { - case Bindings::CompositeOperationOrAuto::Accumulate: - return Bindings::CompositeOperation::Accumulate; - case Bindings::CompositeOperationOrAuto::Add: - return Bindings::CompositeOperation::Add; - case Bindings::CompositeOperationOrAuto::Replace: - return Bindings::CompositeOperation::Replace; - case Bindings::CompositeOperationOrAuto::Auto: - return effect->composite(); - } - VERIFY_NOT_REACHED(); - }; + auto to_composite_operation = [&](Bindings::CompositeOperationOrAuto composite_operation_or_auto) { + switch (composite_operation_or_auto) { + case Bindings::CompositeOperationOrAuto::Accumulate: + return Bindings::CompositeOperation::Accumulate; + case Bindings::CompositeOperationOrAuto::Add: + return Bindings::CompositeOperation::Add; + case Bindings::CompositeOperationOrAuto::Replace: + return Bindings::CompositeOperation::Replace; + case Bindings::CompositeOperationOrAuto::Auto: + return effect->composite(); + } + VERIFY_NOT_REACHED(); + }; - auto is_result_of_transition = animation->is_css_transition() ? AnimatedPropertyResultOfTransition::Yes : AnimatedPropertyResultOfTransition::No; + auto is_result_of_transition = animation->is_css_transition() ? AnimatedPropertyResultOfTransition::Yes : AnimatedPropertyResultOfTransition::No; - Vector>> keyframe_computed_values; - keyframe_computed_values.resize(ordered_keyframes.size()); - auto computed_values_for_keyframe = [&](size_t index) -> HashMap> const& { - if (keyframe_computed_values[index].is_empty()) - keyframe_computed_values[index] = compute_keyframe_values(*ordered_keyframes[index].frame); - return keyframe_computed_values[index]; - }; + Vector>> keyframe_computed_values; + keyframe_computed_values.resize(ordered_keyframes.size()); + auto computed_values_for_keyframe = [&](size_t index) -> HashMap> const& { + if (keyframe_computed_values[index].is_empty()) + keyframe_computed_values[index] = compute_keyframe_values(*ordered_keyframes[index].frame); + return keyframe_computed_values[index]; + }; - VERIFY(computation_context_cache_is_empty()); - auto const& color_computation_context = get_computation_context_for_property(PropertyID::Color, computed_properties, abstract_element); - ColorResolutionContext color_resolution_context { - .color_scheme = color_computation_context.color_scheme, - .current_color = InitialValues::color(), - .current_color_style_value = &computed_properties.property(PropertyID::Color), - .calculation_resolution_context = { .length_resolution_context = color_computation_context.length_resolution_context }, - }; - color_resolution_context.current_color = computed_properties.color(PropertyID::Color, color_resolution_context); + for (auto const& [property_id, specifying_keyframes] : keyframes_specifying_property) { + // A property is usually specified by at least the initial and final keyframes, but a value that stays + // unresolved may leave a property with only one specifying keyframe. Such a property cannot be interpolated, so skip it. + if (specifying_keyframes.size() < 2) + continue; - for (auto const& [property_id, specifying_keyframes] : keyframes_specifying_property) { - // A property is usually specified by at least the initial and final keyframes, but a value that stays - // unresolved may leave a property with only one specifying keyframe. Such a property cannot be interpolated, so skip it. - if (specifying_keyframes.size() < 2) - continue; + // An unresolved shorthand cannot be expanded while building the property index. Computing its keyframe + // values either resolves it into physical longhands or leaves no value, so it is never itself animatable. + if (property_id < first_longhand_property_id || property_id > last_longhand_property_id) + continue; - auto start_keyframe = specifying_keyframes[0]; - auto end_keyframe = specifying_keyframes[1]; - for (size_t next = 2; next < specifying_keyframes.size(); ++next) { - if (current_key < ordered_keyframes[end_keyframe].key) - break; - start_keyframe = end_keyframe; - end_keyframe = specifying_keyframes[next]; + PreparedAnimationValue prepared_value { + .effect = effect, + .property_id = property_id, + .is_result_of_transition = is_result_of_transition, + .underlying = computed_properties.property(property_id), + .initial = property_initial_value(property_id), + .current_key = current_key, + .keyframes = {}, + }; + prepared_value.keyframes.ensure_capacity(specifying_keyframes.size()); + for (auto keyframe_index : specifying_keyframes) { + prepared_value.keyframes.unchecked_append({ + .key = ordered_keyframes[keyframe_index].key, + .value = computed_values_for_keyframe(keyframe_index).get(property_id).value_or(nullptr), + .easing = resolve_interval_easing(ordered_keyframes[keyframe_index].frame->easing), + .composite_operation = to_composite_operation(ordered_keyframes[keyframe_index].frame->composite), + }); + } + prepared_values.append(move(prepared_value)); + } } - auto start_key = ordered_keyframes[start_keyframe].key; - auto end_key = ordered_keyframes[end_keyframe].key; - double interval_progress = (static_cast(current_key) - start_key) / static_cast(end_key - start_key); - interval_progress = apply_keyframe_easing(ordered_keyframes[start_keyframe].frame->easing, interval_progress); - - RefPtr resolved_start_property = computed_values_for_keyframe(start_keyframe).get(property_id).value_or(nullptr); - RefPtr resolved_end_property = computed_values_for_keyframe(end_keyframe).get(property_id).value_or(nullptr); + if (prepared_values.is_empty()) { + return {}; + } - if (!resolved_end_property) { - if (resolved_start_property) { - computed_properties.set_animated_property(Badge {}, property_id, *resolved_start_property, is_result_of_transition); - dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "No end property for property {}, using {}", string_from_property_id(property_id), resolved_start_property->to_string(SerializationMode::Normal)); + auto const& color_computation_context = get_computation_context_for_property(PropertyID::Color, computed_properties, abstract_element); + ColorResolutionContext color_resolution_context { + .color_scheme = color_computation_context.color_scheme, + .current_color = InitialValues::color(), + .current_color_style_value = &computed_properties.property(PropertyID::Color), + .calculation_resolution_context = { .length_resolution_context = color_computation_context.length_resolution_context }, + }; + color_resolution_context.current_color = computed_properties.color(PropertyID::Color, color_resolution_context); + + auto ffi_composite_operation = [](Bindings::CompositeOperation operation) { + switch (operation) { + case Bindings::CompositeOperation::Replace: + return StyleValueFFI::FfiCompositeOperation::Replace; + case Bindings::CompositeOperation::Add: + return StyleValueFFI::FfiCompositeOperation::Add; + case Bindings::CompositeOperation::Accumulate: + return StyleValueFFI::FfiCompositeOperation::Accumulate; } - continue; + VERIFY_NOT_REACHED(); + }; + ffi_keyframes.resize(prepared_values.size()); + linear_easing_points.resize(prepared_values.size()); + ffi_values.ensure_capacity(prepared_values.size()); + for (size_t index = 0; index < prepared_values.size(); ++index) { + auto const& value = prepared_values[index]; + auto& keyframes = ffi_keyframes[index]; + auto& property_linear_easing_points = linear_easing_points[index]; + keyframes.ensure_capacity(value.keyframes.size()); + property_linear_easing_points.resize(value.keyframes.size()); + for (size_t keyframe_index = 0; keyframe_index < value.keyframes.size(); ++keyframe_index) { + auto const& keyframe = value.keyframes[keyframe_index]; + auto easing = keyframe.easing.visit( + [&](LinearEasingFunction const& linear) { + auto& points = property_linear_easing_points[keyframe_index]; + points.ensure_capacity(linear.control_points.size()); + for (auto const& point : linear.control_points) { + VERIFY(point.input.has_value()); + points.unchecked_append({ .input = *point.input, .output = point.output }); + } + return StyleValueFFI::FfiEasingDescriptor { + .kind = StyleValueFFI::FfiEasingKind::Linear, + .linear_points = points.data(), + .linear_point_count = points.size(), + .x1 = 0, + .y1 = 0, + .x2 = 0, + .y2 = 0, + .interval_count = 0, + .step_position = 0, + }; + }, + [](CubicBezierEasingFunction const& cubic_bezier) { + return StyleValueFFI::FfiEasingDescriptor { + .kind = StyleValueFFI::FfiEasingKind::CubicBezier, + .linear_points = nullptr, + .linear_point_count = 0, + .x1 = cubic_bezier.x1, + .y1 = cubic_bezier.y1, + .x2 = cubic_bezier.x2, + .y2 = cubic_bezier.y2, + .interval_count = 0, + .step_position = 0, + }; + }, + [](StepsEasingFunction const& steps) { + return StyleValueFFI::FfiEasingDescriptor { + .kind = StyleValueFFI::FfiEasingKind::Steps, + .linear_points = nullptr, + .linear_point_count = 0, + .x1 = 0, + .y1 = 0, + .x2 = 0, + .y2 = 0, + .interval_count = steps.interval_count, + .step_position = to_underlying(steps.position), + }; + }); + keyframes.unchecked_append({ + .key = keyframe.key, + .value = keyframe.value ? keyframe.value->rust_style_value_data() : nullptr, + .easing = easing, + .composite = ffi_composite_operation(keyframe.composite_operation), + }); + } + ffi_values.unchecked_append({ + .property_id = to_underlying(value.property_id), + .underlying = value.underlying->rust_style_value_data(), + .initial = value.initial->rust_style_value_data(), + .current_key = value.current_key, + .keyframes = keyframes.data(), + .keyframe_count = keyframes.size(), + }); } - if (resolved_end_property && !resolved_start_property) - resolved_start_property = property_initial_value(property_id); - - if (!resolved_start_property || !resolved_end_property) - continue; - - auto start = resolved_start_property.release_nonnull(); - auto end = resolved_end_property.release_nonnull(); - - // OPTIMIZATION: Values resulting from animations other than CSS transitions are overridden by important - // properties so there's no need to calculate them - if (!animation->is_css_transition() && computed_properties.is_property_important(property_id)) { - continue; + auto animation_font_metrics = [](Length::FontMetrics const& metrics) { + return StyleValueFFI::FfiAnimationFontMetrics { + .font_size = metrics.font_size.to_double(), + .x_height = metrics.x_height.to_double(), + .cap_height = metrics.cap_height.to_double(), + .zero_advance = metrics.zero_advance.to_double(), + .line_height = metrics.line_height.to_double(), + }; + }; + auto const& resolution_context = color_computation_context.length_resolution_context; + StyleValueFFI::FfiAnimationContext animation_context { + .allow_discrete = true, + .current_color = computed_properties.property(PropertyID::Color).rust_style_value_data(), + .has_length_resolution_context = true, + .length_resolution_context = { + .viewport_width = resolution_context.viewport_rect.width().to_double(), + .viewport_height = resolution_context.viewport_rect.height().to_double(), + .font_metrics = animation_font_metrics(resolution_context.font_metrics), + .root_font_metrics = animation_font_metrics(resolution_context.root_font_metrics), + .font_metrics_depend_on_viewport_metrics = resolution_context.font_metrics_depend_on_viewport_metrics, + .root_font_metrics_depend_on_viewport_metrics = resolution_context.root_font_metrics_depend_on_viewport_metrics, + }, + .has_transform_reference_box = false, + .transform_reference_box_width = 0, + .transform_reference_box_height = 0, + }; + if (auto paintable = prepared_values.first().effect->target()->unsafe_paintable(); paintable) { + auto reference_box = paintable->transform_reference_box(); + animation_context.has_transform_reference_box = true; + animation_context.transform_reference_box_width = reference_box.width().to_double(); + animation_context.transform_reference_box_height = reference_box.height().to_double(); } + ffi_results.resize(ffi_values.size()); + return StyleValueFFI::FfiComputedAnimationBatch { + .context = animation_context, + .values = ffi_values.data(), + .value_count = ffi_values.size(), + .results = ffi_results.data(), + .result_capacity = ffi_results.size(), + }; + }; - auto const& underlying_value = computed_properties.property(property_id); - auto start_composite_operation = to_composite_operation(ordered_keyframes[start_keyframe].frame->composite); - auto end_composite_operation = to_composite_operation(ordered_keyframes[end_keyframe].frame->composite); - - if (auto composited_start_value = composite_value(property_id, underlying_value, start, start_composite_operation, color_resolution_context)) - start = *composited_start_value; - - if (auto composited_end_value = composite_value(property_id, underlying_value, end, end_composite_operation, color_resolution_context)) - end = *composited_end_value; - - if (auto next_value = interpolate_property(*effect->target(), property_id, *start, *end, interval_progress, AllowDiscrete::Yes, &color_resolution_context)) { - dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} = {}", string_from_property_id(property_id), interval_progress, start->to_string(SerializationMode::Normal), end->to_string(SerializationMode::Normal), next_value->to_string(SerializationMode::Normal)); - computed_properties.set_animated_property(Badge {}, property_id, *next_value, is_result_of_transition); + struct AnimationEvaluationContext { + decltype(compute_animation_values)& compute_values; + } evaluation_context { compute_animation_values }; + StyleValueFFI::FfiAnimationBatch batch { + .declarations = ffi_declarations.data(), + .declaration_count = ffi_declarations.size(), + .writing_mode = to_underlying(computed_properties.writing_mode()), + .direction = to_underlying(computed_properties.direction()), + .important_property_bitmap = important_property_bitmap.data(), + .important_property_bitmap_length = important_property_bitmap.size(), + }; + StyleValueFFI::FfiAnimationCallbacks callbacks { + .context = &evaluation_context, + .compute_values = [](void* context, StyleValueFFI::FfiResolvedAnimationProperty const* properties, size_t property_count) { + auto& evaluation_context = *static_cast(context); + return evaluation_context.compute_values(ReadonlySpan { properties, property_count }); }, + }; + auto result_count = StyleValueFFI::rust_evaluate_animations(&batch, &callbacks); + VERIFY(result_count == prepared_values.size()); + VERIFY(result_count == ffi_results.size()); + for (size_t index = 0; index < result_count; ++index) { + auto const& value = ffi_results[index]; + auto& prepared_value = prepared_values[index]; + VERIFY(value.property_id == to_underlying(prepared_value.property_id)); + VERIFY(value.handled); + if (!value.apply) + continue; + if (value.value) { + auto style_value = StyleValue::adopt_rust_style_value_data(value.value); + computed_properties.set_animated_property(Badge {}, prepared_value.property_id, style_value, prepared_value.is_result_of_transition); } else { - // If interpolate_property() fails, the element should not be rendered - dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} is invalid", string_from_property_id(property_id), interval_progress, start->to_string(SerializationMode::Normal), end->to_string(SerializationMode::Normal)); - computed_properties.set_animated_property(Badge {}, PropertyID::Visibility, KeywordStyleValue::create(Keyword::Hidden), is_result_of_transition); + // NB: If interpolation fails, the element should not be rendered. + computed_properties.set_animated_property(Badge {}, PropertyID::Visibility, KeywordStyleValue::create(Keyword::Hidden), prepared_value.is_result_of_transition); } } @@ -1437,13 +1587,6 @@ void StyleComputer::start_needed_transitions(ComputedValues const& previous_styl { auto& new_style = new_style_builder.style(); - // https://drafts.csswg.org/css-transitions/#transition-combined-duration - auto combined_duration = [](Animations::Animatable::TransitionAttributes const& transition_attributes) { - // Define the combined duration of the transition as the sum of max(matching transition duration, 0s) and the matching transition delay. - return max(transition_attributes.duration, 0) + transition_attributes.delay; - }; - - // For each element and property, the implementation must act as follows: // NB: We know that a DocumentTimeline's current time is always in milliseconds auto current_time = m_document->timeline()->current_time(); if (!current_time.has_value()) @@ -1453,213 +1596,209 @@ void StyleComputer::start_needed_transitions(ComputedValues const& previous_styl auto after_change_style = build_computed_values(new_style, abstract_element, abstract_element.style_scope()); + auto transition_font_metrics = [](Length::FontMetrics const& metrics) { + return StyleValueFFI::FfiAnimationFontMetrics { + .font_size = metrics.font_size.to_double(), + .x_height = metrics.x_height.to_double(), + .cap_height = metrics.cap_height.to_double(), + .zero_advance = metrics.zero_advance.to_double(), + .line_height = metrics.line_height.to_double(), + }; + }; + auto const& transition_computation_context = get_computation_context_for_property(PropertyID::Color, new_style, abstract_element); + auto const& transition_length_context = transition_computation_context.length_resolution_context; + StyleValueFFI::FfiAnimationContext transition_animation_context { + .allow_discrete = false, + .current_color = new_style.property(PropertyID::Color).rust_style_value_data(), + .has_length_resolution_context = true, + .length_resolution_context = { + .viewport_width = transition_length_context.viewport_rect.width().to_double(), + .viewport_height = transition_length_context.viewport_rect.height().to_double(), + .font_metrics = transition_font_metrics(transition_length_context.font_metrics), + .root_font_metrics = transition_font_metrics(transition_length_context.root_font_metrics), + .font_metrics_depend_on_viewport_metrics = transition_length_context.font_metrics_depend_on_viewport_metrics, + .root_font_metrics_depend_on_viewport_metrics = transition_length_context.root_font_metrics_depend_on_viewport_metrics, + }, + .has_transform_reference_box = false, + .transform_reference_box_width = 0, + .transform_reference_box_height = 0, + }; + if (auto paintable = abstract_element.element().unsafe_paintable(); paintable) { + auto reference_box = paintable->transform_reference_box(); + transition_animation_context.has_transform_reference_box = true; + transition_animation_context.transform_reference_box_width = reference_box.width().to_double(); + transition_animation_context.transform_reference_box_height = reference_box.height().to_double(); + } + clear_computation_context_caches(); + // FIXME: Add some transition helpers to AbstractElement. auto& element = abstract_element.element(); auto pseudo_element = abstract_element.pseudo_element(); - // OPTIMIZATION: Instead of iterating over all properties we split the logic into two loops, one for the properties - // which appear in transition-property and one for those which have existing transitions - for (auto property_id : element.property_ids_with_matching_transition_property_entry(pseudo_element)) { - auto matching_transition_properties = element.property_transition_attributes(pseudo_element, property_id).value(); - auto before_change_style_value = previous_style.computed_style_value(property_id, ComputedValues::WithAnimationsApplied::Yes); - auto after_change_style_value = after_change_style->computed_style_value(property_id, ComputedValues::WithAnimationsApplied::No); - VERIFY(before_change_style_value); - VERIFY(after_change_style_value); - auto const& before_change_value = *before_change_style_value; - auto const& after_change_value = *after_change_style_value; - auto originates_from_current_color = [](ComputedValues const& style, PropertyID property_id) { - auto value = style.inheritance_dependent_specified_values().get(property_id); - return value.has_value() && value.value()->to_keyword() == Keyword::Currentcolor; - }; - bool before_change_style_is_different = !before_change_value.equals(after_change_value); - if (originates_from_current_color(previous_style, property_id) && originates_from_current_color(*after_change_style, property_id)) - before_change_style_is_different = false; + struct PreparedTransition { + PropertyID property_id; + RefPtr before_change_value; + RefPtr after_change_value; + RefPtr current_value; + GC::Ptr existing_transition; + }; + Vector prepared_transitions; + Vector ffi_properties; + enum class HasMatchingTransition { + No, + Yes, + }; + auto append_transition_input = [&](PropertyID property_id, HasMatchingTransition has_matching_transition) { auto existing_transition = element.property_transition(pseudo_element, property_id); bool has_running_transition = existing_transition && !existing_transition->is_finished() && !existing_transition->is_idle(); - bool has_completed_transition = existing_transition && (existing_transition->is_finished() || existing_transition->is_idle()); - - auto start_a_transition = [&](auto delay, auto start_time, auto end_time, auto const& start_value, auto const& end_value, auto const& reversing_adjusted_start_value, auto reversing_shortening_factor) { - dbgln_if(CSS_TRANSITIONS_DEBUG, "Starting a transition of {} from {} to {}", string_from_property_id(property_id), start_value.to_string(SerializationMode::Normal), end_value.to_string(SerializationMode::Normal)); - - auto transition = CSSTransition::start_a_transition(abstract_element, property_id, - document().transition_generation(), delay, start_time, end_time, start_value, end_value, reversing_adjusted_start_value, reversing_shortening_factor); - // Immediately set the property's value to the transition's current value, to prevent single-frame jumps. - collect_animation_into(abstract_element, as(*transition->effect()), new_style_builder); - }; - - // 1. If all of the following are true: - if ( - // - the element does not have a running transition for the property, - (!has_running_transition) && - // - there is a matching transition-property value, and - // NOTE: We only iterate over properties for which this is true - // - the before-change style is different from the after-change style for that property, and the values for the property are transitionable, - (before_change_style_is_different && property_values_are_transitionable(property_id, before_change_value, after_change_value, element, matching_transition_properties.transition_behavior)) && - // - the element does not have a completed transition for the property - // or the end value of the completed transition is different from the after-change style for the property, - (!has_completed_transition || !existing_transition->transition_end_value()->equals(after_change_value)) && - // - the combined duration is greater than 0s, - (combined_duration(matching_transition_properties) > 0)) { - - dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 1."); - - // then implementations must remove the completed transition (if present) from the set of completed transitions - if (has_completed_transition) - element.remove_transition(pseudo_element, property_id); - // and start a transition whose: - - // AD-HOC: We pass delay to the constructor separately so we can use it to construct the contained KeyframeEffect - auto delay = matching_transition_properties.delay; - - // - start time is the time of the style change event plus the matching transition delay, - auto start_time = style_change_event_time; - - // - end time is the start time plus the matching transition duration, - auto end_time = start_time + matching_transition_properties.duration; - - // - start value is the value of the transitioning property in the before-change style, - auto const& start_value = before_change_value; - - // - end value is the value of the transitioning property in the after-change style, - auto const& end_value = after_change_value; - - // - reversing-adjusted start value is the same as the start value, and - auto const& reversing_adjusted_start_value = start_value; - - // - reversing shortening factor is 1. - double reversing_shortening_factor = 1; - - start_a_transition(delay, start_time, end_time, start_value, end_value, reversing_adjusted_start_value, reversing_shortening_factor); - } - - // 2. Otherwise, if the element has a completed transition for the property - // and the end value of the completed transition is different from the after-change style for the property, - // then implementations must remove the completed transition from the set of completed transitions. - else if (has_completed_transition && !existing_transition->transition_end_value()->equals(after_change_value)) { - dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 2."); - element.remove_transition(pseudo_element, property_id); - } - - // NOTE: Step 3 is handled in a separate loop below for performance reasons - - // 4. If the element has a running transition for the property, - // there is a matching transition-property value, - // and the end value of the running transition is not equal to the value of the property in the after-change style, then: - if (has_running_transition && !existing_transition->transition_end_value()->equals(after_change_value)) { - dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 4. existing end value = {}, after change value = {}", existing_transition->transition_end_value()->to_string(SerializationMode::Normal), after_change_value.to_string(SerializationMode::Normal)); - // 1. If the current value of the property in the running transition is equal to the value of the property in the after-change style, - // or if these two values are not transitionable, - // then implementations must cancel the running transition. - auto current_style_value = after_change_style->computed_style_value(property_id, ComputedValues::WithAnimationsApplied::Yes); - VERIFY(current_style_value); - auto const& current_value = *current_style_value; - if (current_value.equals(after_change_value) || !property_values_are_transitionable(property_id, current_value, after_change_value, element, matching_transition_properties.transition_behavior)) { - dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 4.1"); - existing_transition->cancel(); - } - - // 2. Otherwise, if the combined duration is less than or equal to 0s, - // or if the current value of the property in the running transition is not transitionable with the value of the property in the after-change style, - // then implementations must cancel the running transition. - else if ((combined_duration(matching_transition_properties) <= 0) - || !property_values_are_transitionable(property_id, current_value, after_change_value, element, matching_transition_properties.transition_behavior)) { - dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 4.2"); - existing_transition->cancel(); + bool has_completed_transition = existing_transition && !has_running_transition; + RefPtr before_change_value; + RefPtr after_change_value; + RefPtr current_value; + bool before_change_value_originates_from_current_color = false; + bool after_change_value_originates_from_current_color = false; + bool allow_discrete = false; + double delay = 0; + double duration = 0; + double old_timing_function_output = 0; + double old_reversing_shortening_factor = 1; + + if (has_matching_transition == HasMatchingTransition::Yes) { + auto transition_attributes = element.property_transition_attributes(pseudo_element, property_id).value(); + delay = transition_attributes.delay; + duration = transition_attributes.duration; + allow_discrete = transition_attributes.transition_behavior == TransitionBehavior::AllowDiscrete; + before_change_value = previous_style.computed_style_value(property_id, ComputedValues::WithAnimationsApplied::Yes); + after_change_value = after_change_style->computed_style_value(property_id, ComputedValues::WithAnimationsApplied::No); + VERIFY(before_change_value); + VERIFY(after_change_value); + + auto originates_from_current_color = [](ComputedValues const& style, PropertyID property_id) { + auto value = style.inheritance_dependent_specified_values().get(property_id); + return value.has_value() && value.value()->to_keyword() == Keyword::Currentcolor; + }; + before_change_value_originates_from_current_color = originates_from_current_color(previous_style, property_id); + after_change_value_originates_from_current_color = originates_from_current_color(*after_change_style, property_id); + if (existing_transition) { + old_reversing_shortening_factor = existing_transition->reversing_shortening_factor(); + if (has_running_transition) + old_timing_function_output = existing_transition->timing_function_output_at_time(style_change_event_time); } - - // 3. Otherwise, if the reversing-adjusted start value of the running transition is the same as the value of the property in the after-change style - // (see the section on reversing of transitions for why these case exists), - else if (existing_transition->reversing_adjusted_start_value()->equals(after_change_value)) { - dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 4.3"); - // implementations must cancel the running transition and start a new transition whose: - existing_transition->cancel(); - // AD-HOC: Remove the cancelled transition, otherwise it breaks the invariant that there is only one - // running or completed transition for a property at once. - element.remove_transition(pseudo_element, property_id); - - // - reversing-adjusted start value is the end value of the running transition, - auto reversing_adjusted_start_value = existing_transition->transition_end_value(); - - // - reversing shortening factor is the absolute value, clamped to the range [0, 1], of the sum of: - // 1. the output of the timing function of the old transition at the time of the style change event, - // times the reversing shortening factor of the old transition - auto term_1 = existing_transition->timing_function_output_at_time(style_change_event_time) * existing_transition->reversing_shortening_factor(); - // 2. 1 minus the reversing shortening factor of the old transition. - auto term_2 = 1 - existing_transition->reversing_shortening_factor(); - double reversing_shortening_factor = clamp(abs(term_1 + term_2), 0.0, 1.0); - - // AD-HOC: We pass delay to the constructor separately so we can use it to construct the contained KeyframeEffect - auto delay = (matching_transition_properties.delay >= 0 - ? (matching_transition_properties.delay) - : (reversing_shortening_factor * matching_transition_properties.delay)); - - // - start time is the time of the style change event plus: - // 1. if the matching transition delay is nonnegative, the matching transition delay, or - // 2. if the matching transition delay is negative, the product of the new transition’s reversing shortening factor and the matching transition delay, - auto start_time = style_change_event_time; - - // - end time is the start time plus the product of the matching transition duration and the new transition’s reversing shortening factor, - auto end_time = start_time + (matching_transition_properties.duration * reversing_shortening_factor); - - // - start value is the current value of the property in the running transition, - auto const& start_value = current_value; - - // - end value is the value of the property in the after-change style, - auto const& end_value = after_change_value; - - start_a_transition(delay, start_time, end_time, start_value, end_value, reversing_adjusted_start_value, reversing_shortening_factor); + if (has_running_transition) { + current_value = after_change_style->computed_style_value(property_id, ComputedValues::WithAnimationsApplied::Yes); + VERIFY(current_value); } + } - // 4. Otherwise, - else { - dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 4.4"); - // implementations must cancel the running transition and start a new transition whose: - existing_transition->cancel(); - // AD-HOC: Remove the cancelled transition, otherwise it breaks the invariant that there is only one - // running or completed transition for a property at once. - element.remove_transition(pseudo_element, property_id); - - // AD-HOC: We pass delay to the constructor separately so we can use it to construct the contained KeyframeEffect - auto delay = matching_transition_properties.delay; - - // - start time is the time of the style change event plus the matching transition delay, - auto start_time = style_change_event_time; - - // - end time is the start time plus the matching transition duration, - auto end_time = start_time + matching_transition_properties.duration; - - // - start value is the current value of the property in the running transition, - auto const& start_value = current_value; - - // - end value is the value of the property in the after-change style, - auto const& end_value = after_change_value; + ffi_properties.append({ + .property_id = to_underlying(property_id), + .before_change_value = before_change_value ? before_change_value->rust_style_value_data() : nullptr, + .after_change_value = after_change_value ? after_change_value->rust_style_value_data() : nullptr, + .current_value = current_value ? current_value->rust_style_value_data() : nullptr, + .existing_end_value = existing_transition ? existing_transition->transition_end_value()->rust_style_value_data() : nullptr, + .reversing_adjusted_start_value = existing_transition ? existing_transition->reversing_adjusted_start_value()->rust_style_value_data() : nullptr, + .has_matching_transition = has_matching_transition == HasMatchingTransition::Yes, + .allow_discrete = allow_discrete, + .before_change_value_originates_from_current_color = before_change_value_originates_from_current_color, + .after_change_value_originates_from_current_color = after_change_value_originates_from_current_color, + .has_running_transition = has_running_transition, + .has_completed_transition = has_completed_transition, + .delay = delay, + .duration = duration, + .old_timing_function_output = old_timing_function_output, + .old_reversing_shortening_factor = old_reversing_shortening_factor, + }); + prepared_transitions.append({ + .property_id = property_id, + .before_change_value = move(before_change_value), + .after_change_value = move(after_change_value), + .current_value = move(current_value), + .existing_transition = existing_transition, + }); + }; - // - reversing-adjusted start value is the same as the start value, and - auto const& reversing_adjusted_start_value = start_value; + // OPTIMIZATION: Instead of iterating over all properties we collect properties which appear in + // transition-property, followed by existing transitions without a matching entry. + for (auto property_id : element.property_ids_with_matching_transition_property_entry(pseudo_element)) + append_transition_input(property_id, HasMatchingTransition::Yes); + for (auto property_id : element.property_ids_with_existing_transitions(pseudo_element)) { + if (!element.property_transition_attributes(pseudo_element, property_id).has_value()) + append_transition_input(property_id, HasMatchingTransition::No); + } - // - reversing shortening factor is 1. - double reversing_shortening_factor = 1; + StyleValueFFI::FfiTransitionInput input { + .context = transition_animation_context, + .properties = ffi_properties.data(), + .property_count = ffi_properties.size(), + }; + Vector actions; + actions.resize(prepared_transitions.size()); + StyleValueFFI::rust_decide_transitions(&input, actions.data()); + + Vector> newly_started_transition_effects; + for (size_t index = 0; index < prepared_transitions.size(); ++index) { + auto const& prepared_transition = prepared_transitions[index]; + auto property_id = prepared_transition.property_id; + auto const& action = actions[index]; + VERIFY(action.property_id == to_underlying(property_id)); + auto existing_transition = prepared_transition.existing_transition; + auto remove_existing_transition = [&] { + element.remove_transition(pseudo_element, property_id); + }; + auto cancel_and_remove_existing_transition = [&] { + VERIFY(existing_transition); + existing_transition->cancel(); + // AD-HOC: Remove the cancelled transition, otherwise it breaks the invariant that there is only one + // running or completed transition for a property at once. + remove_existing_transition(); + }; + auto start_a_transition = [&](StyleValue const& start_value, StyleValue const& end_value, StyleValue const& reversing_adjusted_start_value) { + dbgln_if(CSS_TRANSITIONS_DEBUG, "Starting a transition of {} from {} to {}", string_from_property_id(property_id), start_value.to_string(SerializationMode::Normal), end_value.to_string(SerializationMode::Normal)); + auto start_time = style_change_event_time; + auto end_time = start_time + action.active_duration; + auto transition = CSSTransition::start_a_transition(abstract_element, property_id, + document().transition_generation(), action.delay, start_time, end_time, start_value, end_value, reversing_adjusted_start_value, action.reversing_shortening_factor); + newly_started_transition_effects.append(as(*transition->effect())); + }; - start_a_transition(delay, start_time, end_time, start_value, end_value, reversing_adjusted_start_value, reversing_shortening_factor); - } + switch (action.kind) { + case StyleValueFFI::FfiTransitionActionKind::None: + break; + case StyleValueFFI::FfiTransitionActionKind::Remove: + remove_existing_transition(); + break; + case StyleValueFFI::FfiTransitionActionKind::Cancel: + VERIFY(existing_transition); + existing_transition->cancel(); + break; + case StyleValueFFI::FfiTransitionActionKind::Start: + start_a_transition(*prepared_transition.before_change_value, *prepared_transition.after_change_value, *prepared_transition.before_change_value); + break; + case StyleValueFFI::FfiTransitionActionKind::RemoveAndStart: + remove_existing_transition(); + start_a_transition(*prepared_transition.before_change_value, *prepared_transition.after_change_value, *prepared_transition.before_change_value); + break; + case StyleValueFFI::FfiTransitionActionKind::CancelRemoveAndStartReversing: { + VERIFY(existing_transition); + auto reversing_adjusted_start_value = existing_transition->transition_end_value(); + cancel_and_remove_existing_transition(); + start_a_transition(*prepared_transition.current_value, *prepared_transition.after_change_value, *reversing_adjusted_start_value); + break; + } + case StyleValueFFI::FfiTransitionActionKind::CancelRemoveAndStartInterrupted: + cancel_and_remove_existing_transition(); + start_a_transition(*prepared_transition.current_value, *prepared_transition.after_change_value, *prepared_transition.current_value); + break; } } - for (auto property_id : element.property_ids_with_existing_transitions(pseudo_element)) { - // 3. If the element has a running transition or completed transition for the property, and there is not a - // matching transition-property value, then implementations must cancel the running transition or remove the - // completed transition from the set of completed transitions. - if (element.property_transition_attributes(pseudo_element, property_id).has_value()) - continue; - - auto const& existing_transition = element.property_transition(pseudo_element, property_id); - - dbgln_if(CSS_TRANSITIONS_DEBUG, "Transition step 3."); - if (!existing_transition->is_finished() && !existing_transition->is_idle()) - existing_transition->cancel(); - else - element.remove_transition(pseudo_element, property_id); + // Immediately set the properties to the transitions' current values, to prevent single-frame jumps. + if (!newly_started_transition_effects.is_empty()) { + collect_animations_into(abstract_element, newly_started_transition_effects.span(), new_style_builder); + // NB: Construction does not invalidate animated style because the effects were just evaluated. Request the + // first animation frame directly so timeline updates can schedule subsequent animated style updates. + m_document->page().client().request_frame(); } } @@ -2227,7 +2366,7 @@ NonnullRefPtr StyleComputer::compute_cascaded_values(DOM::Ab all_declarations.unchecked_append({ .property_id = to_underlying(property.property_id), .important = property.important == Important::Yes, - .shell = property.value.ptr(), + .has_style_sheet_context = property.value->has_style_sheet_context(), .data = property.value->rust_style_value_data(), }); } @@ -2241,7 +2380,7 @@ NonnullRefPtr StyleComputer::compute_cascaded_values(DOM::Ab .name_raw = name_raw, .important = property.important == Important::Yes, .is_revert_layer = property.value->is_revert_layer(), - .shell = property.value.ptr(), + .data = property.value->rust_style_value_data(), }); } } @@ -2349,17 +2488,19 @@ NonnullRefPtr StyleComputer::compute_cascaded_values(DOM::Ab ComputedValuesFFI::FfiBulkCascadeCallbacks const callbacks { .context = &bulk_context, - .resolve_unresolved = [](void* context, u16 property_id, void const* shell) -> ComputedValuesFFI::FfiResolvedStyleValue { + .resolve_unresolved = [](void* context, u16 property_id, void const* data) -> ComputedValuesFFI::FfiResolvedStyleValue { auto& bulk_context = *static_cast(context); + auto unresolved = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(data))); auto resolved = Parser::Parser::resolve_unresolved_style_value( Parser::ParsingParams { bulk_context.abstract_element.document() }, bulk_context.abstract_element, {}, PropertyNameAndID::from_id(static_cast(property_id)), - static_cast(shell)->as_unresolved()); + unresolved->as_unresolved()); ComputedValuesFFI::FfiResolvedStyleValue result { - .shell = resolved.ptr(), .data = resolved->rust_style_value_data(), + .has_style_sheet_context = resolved->has_style_sheet_context(), }; bulk_context.pinned_values.append(move(resolved)); return result; @@ -2375,26 +2516,12 @@ NonnullRefPtr StyleComputer::compute_cascaded_values(DOM::Ab ? parsed.release_nonnull() : GuaranteedInvalidStyleValue::create(); ComputedValuesFFI::FfiResolvedStyleValue result { - .shell = resolved.ptr(), .data = resolved->rust_style_value_data(), + .has_style_sheet_context = resolved->has_style_sheet_context(), }; bulk_context.pinned_values.append(move(resolved)); return result; }, - .data_of = [](void*, void const* shell) -> void const* { - return static_cast(shell)->rust_style_value_data(); - }, - .create_pending_substitution = [](void* context, void const* shell) -> void const* { - auto& bulk_context = *static_cast(context); - auto pending_substitution_value = PendingSubstitutionStyleValue::create(*static_cast(shell)); - auto const* pointer = pending_substitution_value.ptr(); - bulk_context.pinned_values.append(move(pending_substitution_value)); - return pointer; - }, - .pseudo_element_rejects_property = [](void* context, u16 property_id) -> bool { - auto& bulk_context = *static_cast(context); - return !pseudo_element_supports_property(*bulk_context.abstract_element.pseudo_element(), static_cast(property_id)); - }, .assign_source_slots = [](void* context, ComputedValuesFFI::FfiSourceSlotAssignment const* assignments, size_t count) { auto& bulk_context = *static_cast(context); for (size_t i = 0; i < count; ++i) { @@ -2407,12 +2534,14 @@ NonnullRefPtr StyleComputer::compute_cascaded_values(DOM::Ab cascaded_all.ensure_capacity(count); for (size_t i = 0; i < count; ++i) { auto const& property = properties[i]; + auto value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(property.data))); cascaded_all.set( Utf16FlyString::from_raw(property.name_raw), StyleProperty { .important = property.important ? Important::Yes : Important::No, .property_id = PropertyID::Custom, - .value = *static_cast(property.shell), + .value = move(value), }); } @@ -2425,7 +2554,7 @@ NonnullRefPtr StyleComputer::compute_cascaded_values(DOM::Ab for (auto& [name, property] : cascaded_all) { if (parent_data) { auto const* parent_property = parent_data->get(name); - if (parent_property && parent_property->value.ptr() == property.value.ptr()) + if (parent_property && parent_property->value->rust_style_value_data() == property.value->rust_style_value_data()) continue; } cascaded_own.set(name, move(property)); @@ -2445,18 +2574,13 @@ NonnullRefPtr StyleComputer::compute_cascaded_values(DOM::Ab }, }; - auto cascade_custom_properties = !abstract_element.pseudo_element().has_value() - || pseudo_element_supports_property(*abstract_element.pseudo_element(), PropertyID::Custom); - ComputedValuesFFI::rust_cascade_matched_blocks( cascaded_properties->rust_store(), blocks.data(), blocks.size(), static_cast(matching_rule_set.author_contexts.size()), - abstract_element.pseudo_element().has_value(), - cascade_custom_properties, + pseudo_element_to_ffi(abstract_element.pseudo_element()), abstract_element.document().rust_custom_property_registry(), - unset_value.ptr(), unset_value->rust_style_value_data(), &callbacks); @@ -3185,9 +3309,7 @@ RefPtr StyleComputer::compute_style_impl(DOM::AbstractElemen static bool is_monospace(StyleValue const& value) { - return ComputedValuesFFI::rust_font_family_is_monospace( - value.rust_style_value_data(), - [](void const* shell) -> void const* { return static_cast(shell)->rust_style_value_data(); }); + return ComputedValuesFFI::rust_font_family_is_monospace(value.rust_style_value_data()); } // HACK: This function implements time-travelling inheritance for the font-size property @@ -3337,16 +3459,13 @@ void StyleComputer::ensure_style_metadata_tables_installed() } ComputedValuesFFI::rust_style_metadata_set_physical_to_logical_table(reverse_table.data(), reverse_table.size()); - // Pin every longhand's initial value for the process lifetime and hand the - // (shell, data) pointer pairs to the core, so initial-value selection never - // crosses the FFI. - static NeverDestroyed>> initial_value_pins; - Vector initial_value_entries; + // Transfer one shared Rust reference for every longhand initial value, so + // initial-value selection never crosses the FFI. + Vector initial_value_entries; initial_value_entries.ensure_capacity(number_of_longhand_properties); for (auto i = to_underlying(first_longhand_property_id); i <= to_underlying(last_longhand_property_id); ++i) { auto initial_value = property_initial_value(static_cast(i)); - initial_value_entries.unchecked_append({ initial_value.ptr(), initial_value->rust_style_value_data() }); - initial_value_pins->append(move(initial_value)); + initial_value_entries.unchecked_append(StyleValueFFI::rust_style_value_retain(initial_value->rust_style_value_data())); } ComputedValuesFFI::rust_style_metadata_set_initial_value_table(initial_value_entries.data(), initial_value_entries.size()); @@ -3415,26 +3534,18 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac return computed_value; }; - Optional logical_alias_mapping_context; - auto const get_logical_alias_mapping_context = [&]() { - if (!logical_alias_mapping_context.has_value()) - logical_alias_mapping_context = LogicalAliasMappingContext { computed_style.writing_mode(), computed_style.direction() }; - - return *logical_alias_mapping_context; - }; - // The parent's inheritable computed values, prepared once so the driver's inherit // path never crosses the FFI. Every entry is pinned for the duration of the drive. constexpr size_t inherited_longhand_count = to_underlying(last_inherited_property_id) - to_underlying(first_inherited_property_id) + 1; Array, inherited_longhand_count> parent_snapshot_pins; - Array parent_snapshot_entries {}; + Array parent_snapshot_entries {}; Optional parent_snapshot; if (computed_values_to_inherit_from) { for (size_t index = 0; index < inherited_longhand_count; ++index) { auto property_id = static_cast(to_underlying(first_inherited_property_id) + index); auto value = computed_values_to_inherit_from->computed_style_value_for_inheritance(property_id, ComputedValues::WithAnimationsApplied::No); VERIFY(value); - parent_snapshot_entries[index] = { value.ptr(), value->rust_style_value_data() }; + parent_snapshot_entries[index] = value->rust_style_value_data(); parent_snapshot_pins[index] = move(value); } parent_snapshot = ComputedValuesFFI::FfiParentSnapshot { @@ -3465,23 +3576,31 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac } }; - // Hands the driver the length resolution context a property's computation would - // use, so plain lengths can absolutize natively; the driver caches one per - // context kind, like get_computation_context_for_property does here. - auto fetch_length_resolution_context = [&](PropertyID property_id) { - auto const& computation_context = get_computation_context_for_property(property_id, computed_style, abstract_element); - return to_ffi_length_resolution_context(computation_context.length_resolution_context); - }; - - // Applies a batch of store operations the driver queued, in property order, - // replicating the per-property side effects and performing any computation the - // driver deferred to C++. - auto store_computed_batch = [&](ComputedValuesFFI::FfiComputedStoreEntry const* entries, size_t count) { + Optional display_before_adjustments; + Optional float_before_adjustments; + Optional overflow_x_before_adjustments; + Optional overflow_y_before_adjustments; + Optional text_align_before_adjustments; + Optional position_before_adjustments; + RefPtr line_height_before_adjustments; + // Pins every parent value handed out by an explicit-inherit action until the end + // of the drive; the driver may queue its data in deferred store batches. + Vector> pinned_parent_values; + + // Applies the Rust driver's ordered action batch. Stores precede the optional + // external request so a returned context observes every earlier computed value. + auto execute_computation_batch = [&](ComputedValuesFFI::FfiComputedStoreEntry const* entries, size_t count, i16 effective_color_scheme, u8 request_kind, ComputedValuesFFI::FfiLonghandBatchRequest const* request) { for (size_t i = 0; i < count; ++i) { auto const& entry = entries[i]; auto property_id = static_cast(entry.property_id); auto inherited_property_id = static_cast(entry.inherited_property_id); - auto const& value = *static_cast(entry.shell); + auto retained_value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(entry.data))); + if (entry.source_slot >= 0 && entry.has_style_sheet_context) { + if (auto source = cascaded_properties.source_for_slot(static_cast(entry.source_slot)); source && source->parent_rule()) + const_cast(*retained_value).set_style_sheet(source->parent_rule()->parent_style_sheet()); + } + auto const& value = *retained_value; if (entry.inherited) copy_animated_inherited_value(property_id, inherited_property_id); // Store the resolved specified value for properties whose computation depends on @@ -3534,6 +3653,10 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac } break; } + case ComputedValuesFFI::COMPUTED_KIND_STYLE_VALUE: + builder.set_property_without_modifying_flags(property_id, + StyleValue::adopt_rust_style_value_data(static_cast(entry.computed_data))); + break; default: builder.set_property_without_modifying_flags(property_id, value); break; @@ -3543,79 +3666,46 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac builder.set_effective_color_scheme(static_cast(effective_color_scheme)); } } + if (effective_color_scheme >= 0) + builder.set_effective_color_scheme(static_cast(effective_color_scheme)); + + switch (request_kind) { + case ComputedValuesFFI::LONGHAND_BATCH_REQUEST_NONE: + break; + case ComputedValuesFFI::LONGHAND_BATCH_REQUEST_LENGTH_CONTEXT: { + auto const& computation_context = get_computation_context_for_property(static_cast(request->property_id), computed_style, abstract_element); + *request->out_context = to_ffi_length_resolution_context(computation_context.length_resolution_context); + break; + } + case ComputedValuesFFI::LONGHAND_BATCH_REQUEST_PARENT_VALUE: { + auto value = get_non_animated_inherit_value(static_cast(request->property_id), abstract_element); + *request->out_data = value->rust_style_value_data(); + pinned_parent_values.append(move(value)); + break; + } + case ComputedValuesFFI::LONGHAND_BATCH_REQUEST_POST_COMPUTE_ADJUSTMENTS: { + display_before_adjustments = request->display_before; + float_before_adjustments = static_cast(request->float_before); + overflow_x_before_adjustments = static_cast(request->overflow_x_before); + overflow_y_before_adjustments = static_cast(request->overflow_y_before); + text_align_before_adjustments = static_cast(request->text_align_before); + position_before_adjustments = static_cast(request->position_before); + line_height_before_adjustments = builder.style().property(PropertyID::LineHeight); + builder.set_display_before_box_type_transformation(from_ffi_display(request->display_before)); + *request->out_input_line_height_metrics = input_line_height_metrics(builder, abstract_element, request->check_input_line_height); + break; + } + default: + VERIFY_NOT_REACHED(); + } }; // The property computation flow is driven from the Rust style computation core: it // iterates the longhands in computation order, resolves logical pairing through its // mapping tables, and selects the cascaded, inherited or initial value natively. - struct LonghandLoopContext { - ComputedProperties::Builder& builder; - decltype(store_computed_batch)& store_computed_batch_callback; - decltype(fetch_length_resolution_context)& fetch_length_resolution_context_callback; - decltype(get_logical_alias_mapping_context)& get_logical_alias_mapping_context_callback; - DOM::AbstractElement abstract_element; - Optional display_before_adjustments; - Optional float_before_adjustments; - Optional overflow_x_before_adjustments; - Optional overflow_y_before_adjustments; - Optional text_align_before_adjustments; - Optional position_before_adjustments; - RefPtr line_height_before_adjustments; - // Pins every parent value handed out by the explicit-inherit fetch until the end - // of the drive; the driver may queue the shells in deferred store batches. - Vector> pinned_parent_values; - } loop_context { - .builder = builder, - .store_computed_batch_callback = store_computed_batch, - .fetch_length_resolution_context_callback = fetch_length_resolution_context, - .get_logical_alias_mapping_context_callback = get_logical_alias_mapping_context, - .abstract_element = abstract_element, - .display_before_adjustments = {}, - .float_before_adjustments = {}, - .overflow_x_before_adjustments = {}, - .overflow_y_before_adjustments = {}, - .text_align_before_adjustments = {}, - .position_before_adjustments = {}, - .line_height_before_adjustments = {}, - .pinned_parent_values = {}, - }; - ComputedValuesFFI::FfiLonghandCallbacks const callbacks { - .context = &loop_context, - .store_computed_batch = [](void* context, ComputedValuesFFI::FfiComputedStoreEntry const* entries, size_t count) { - auto& loop_context = *static_cast(context); - loop_context.store_computed_batch_callback(entries, count); }, - .store_effective_color_scheme = [](void* context, u8 color_scheme) { - auto& loop_context = *static_cast(context); - loop_context.builder.set_effective_color_scheme(static_cast(color_scheme)); }, - .prepare_post_compute_adjustments = [](void* context, ComputedValuesFFI::FfiDisplay const* display_before, u16 float_before, u16 overflow_x_before, u16 overflow_y_before, u16 text_align_before, u16 position_before, bool check_input_line_height) -> ComputedValuesFFI::FfiInputLineHeightMetrics { - auto& loop_context = *static_cast(context); - loop_context.display_before_adjustments = *display_before; - loop_context.float_before_adjustments = static_cast(float_before); - loop_context.overflow_x_before_adjustments = static_cast(overflow_x_before); - loop_context.overflow_y_before_adjustments = static_cast(overflow_y_before); - loop_context.text_align_before_adjustments = static_cast(text_align_before); - loop_context.position_before_adjustments = static_cast(position_before); - loop_context.line_height_before_adjustments = loop_context.builder.style().property(PropertyID::LineHeight); - loop_context.builder.set_display_before_box_type_transformation(from_ffi_display(*display_before)); - return input_line_height_metrics(loop_context.builder, loop_context.abstract_element, check_input_line_height); }, - .fetch_non_inherited_parent_value = [](void* context, u16 inherited_property_id) -> ComputedValuesFFI::FfiShellAndData { - auto& loop_context = *static_cast(context); - auto value = get_non_animated_inherit_value(static_cast(inherited_property_id), loop_context.abstract_element); - ComputedValuesFFI::FfiShellAndData entry { value.ptr(), value->rust_style_value_data() }; - loop_context.pinned_parent_values.append(move(value)); - return entry; - }, - .data_of = [](void const* shell) -> void const* { return static_cast(shell)->rust_style_value_data(); }, - .computational_independence_fallback = [](void const* shell) -> bool { return static_cast(shell)->decide_computational_independence_fallback(); }, - .writing_mode_and_direction = [](void* context) -> u16 { - auto& loop_context = *static_cast(context); - auto mapping_context = loop_context.get_logical_alias_mapping_context_callback(); - return static_cast(to_underlying(mapping_context.writing_mode)) | static_cast(to_underlying(mapping_context.direction)) << 8; - }, - .length_resolution_context = [](void* context, u16 property_id, ComputedValuesFFI::FfiLengthResolutionContext* out) { - auto& loop_context = *static_cast(context); - *out = loop_context.fetch_length_resolution_context_callback(static_cast(property_id)); }, + .context = &execute_computation_batch, + .execute_computation_batch = [](void* context, ComputedValuesFFI::FfiComputedStoreEntry const* entries, size_t count, i16 effective_color_scheme, u8 request_kind, ComputedValuesFFI::FfiLonghandBatchRequest const* request) { (*static_cast(context))(entries, count, effective_color_scheme, request_kind, request); }, }; constexpr size_t longhand_bitmap_words = (number_of_longhand_properties + 63) / 64; @@ -3625,7 +3715,7 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac .important_words = important_words.data(), .inherited_words = inherited_words.data(), .word_count = longhand_bitmap_words, - .raw_cascaded_font_size_shell = nullptr, + .raw_cascaded_font_size_data = nullptr, .depends_on_viewport_metrics = false, .font_metrics_depend_on_viewport_metrics = false, .explicitly_inherited_non_inherited_property = false, @@ -3648,8 +3738,9 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac // Store the raw winning cascaded font-size. This is needed to implement the time-traveling inheritance for // font-size when font-family is monospace. // See the recascade_font_size_if_needed() function for further details. - if (driver_results.raw_cascaded_font_size_shell) - builder.set_raw_cascaded_font_size(*static_cast(driver_results.raw_cascaded_font_size_shell)); + if (driver_results.raw_cascaded_font_size_data) + builder.set_raw_cascaded_font_size(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(driver_results.raw_cascaded_font_size_data)))); if (driver_results.depends_on_viewport_metrics) builder.set_depends_on_viewport_metrics(); if (driver_results.font_metrics_depend_on_viewport_metrics) @@ -3673,20 +3764,20 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac process_animation_definitions(computed_style, cascaded_properties, abstract_element); auto restore_values_before_post_compute_adjustments = [&] { - VERIFY(loop_context.display_before_adjustments.has_value()); - VERIFY(loop_context.float_before_adjustments.has_value()); - VERIFY(loop_context.overflow_x_before_adjustments.has_value()); - VERIFY(loop_context.overflow_y_before_adjustments.has_value()); - VERIFY(loop_context.text_align_before_adjustments.has_value()); - VERIFY(loop_context.position_before_adjustments.has_value()); - VERIFY(loop_context.line_height_before_adjustments); - builder.set_property_without_modifying_flags(PropertyID::Display, DisplayStyleValue::create(from_ffi_display(*loop_context.display_before_adjustments))); - builder.set_property_without_modifying_flags(PropertyID::Float, KeywordStyleValue::create(*loop_context.float_before_adjustments)); - builder.set_property_without_modifying_flags(PropertyID::OverflowX, KeywordStyleValue::create(*loop_context.overflow_x_before_adjustments)); - builder.set_property_without_modifying_flags(PropertyID::OverflowY, KeywordStyleValue::create(*loop_context.overflow_y_before_adjustments)); - builder.set_property_without_modifying_flags(PropertyID::TextAlign, KeywordStyleValue::create(*loop_context.text_align_before_adjustments)); - builder.set_property_without_modifying_flags(PropertyID::Position, KeywordStyleValue::create(*loop_context.position_before_adjustments)); - builder.set_property_without_modifying_flags(PropertyID::LineHeight, *loop_context.line_height_before_adjustments); + VERIFY(display_before_adjustments.has_value()); + VERIFY(float_before_adjustments.has_value()); + VERIFY(overflow_x_before_adjustments.has_value()); + VERIFY(overflow_y_before_adjustments.has_value()); + VERIFY(text_align_before_adjustments.has_value()); + VERIFY(position_before_adjustments.has_value()); + VERIFY(line_height_before_adjustments); + builder.set_property_without_modifying_flags(PropertyID::Display, DisplayStyleValue::create(from_ffi_display(*display_before_adjustments))); + builder.set_property_without_modifying_flags(PropertyID::Float, KeywordStyleValue::create(*float_before_adjustments)); + builder.set_property_without_modifying_flags(PropertyID::OverflowX, KeywordStyleValue::create(*overflow_x_before_adjustments)); + builder.set_property_without_modifying_flags(PropertyID::OverflowY, KeywordStyleValue::create(*overflow_y_before_adjustments)); + builder.set_property_without_modifying_flags(PropertyID::TextAlign, KeywordStyleValue::create(*text_align_before_adjustments)); + builder.set_property_without_modifying_flags(PropertyID::Position, KeywordStyleValue::create(*position_before_adjustments)); + builder.set_property_without_modifying_flags(PropertyID::LineHeight, *line_height_before_adjustments); }; if (animation_values_applied) restore_values_before_post_compute_adjustments(); @@ -3697,17 +3788,20 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac if (animations.is_exception()) { dbgln("Error getting animations for element {}", abstract_element.debug_description()); } else { + GC::RootVector> effects; for (auto& animation : animations.value()) { if (auto effect = animation->effect(); effect && effect->is_keyframe_effect()) { auto& keyframe_effect = *static_cast(effect.ptr()); - if (keyframe_effect.pseudo_element_type() == abstract_element.pseudo_element()) { - if (!animation_values_applied) - restore_values_before_post_compute_adjustments(); - animation_values_applied = true; - collect_animation_into(abstract_element, keyframe_effect, builder); - } + if (keyframe_effect.pseudo_element_type() == abstract_element.pseudo_element()) + effects.append(keyframe_effect); } } + if (!effects.is_empty()) { + if (!animation_values_applied) + restore_values_before_post_compute_adjustments(); + animation_values_applied = true; + collect_animations_into(abstract_element, effects.span(), builder); + } } bool parent_text_align_input_is_animated = false; @@ -3718,8 +3812,8 @@ NonnullRefPtr StyleComputer::compute_properties(DOM::Abstrac } } if (parent_text_align_input_is_animated && !animation_values_applied) { - VERIFY(loop_context.text_align_before_adjustments.has_value()); - builder.set_property_without_modifying_flags(PropertyID::TextAlign, KeywordStyleValue::create(*loop_context.text_align_before_adjustments)); + VERIFY(text_align_before_adjustments.has_value()); + builder.set_property_without_modifying_flags(PropertyID::TextAlign, KeywordStyleValue::create(*text_align_before_adjustments)); } // Run automatic box type transformations again after animations have been applied. @@ -4381,8 +4475,6 @@ NonnullRefPtr StyleComputer::compute_value_of_property( return compute_line_height(absolutized_value, computation_context.length_resolution_context.font_metrics.font_size); case PropertyID::MathDepth: return compute_math_depth(absolutized_value, inheritance_parent()); - case PropertyID::PositionArea: - return compute_position_area(absolutized_value); default: return absolutized_value; } @@ -4424,34 +4516,8 @@ NonnullRefPtr StyleComputer::compute_font_feature_tag_value_li if (absolutized_value->is_keyword()) return absolutized_value; - // The deduplication and sorting live in the Rust style computation core; it works over the - // entry indices and calls back for the interned-fly-string tag comparisons. - auto values = absolutized_value->as_value_list().values(); - struct TagContext { - StyleValueVector const& values; - } context { values }; - - Vector order; - order.resize(values.size()); - auto count = ComputedValuesFFI::rust_font_feature_settings_computed_order( - values.size(), - &context, - [](void* context, size_t i, size_t j) -> bool { - auto const& values = static_cast(context)->values; - return values[i]->as_open_type_tagged().tag() == values[j]->as_open_type_tagged().tag(); - }, - [](void* context, size_t i, size_t j) -> bool { - auto const& values = static_cast(context)->values; - return values[i]->as_open_type_tagged().tag().operator<=>(values[j]->as_open_type_tagged().tag()) < 0; - }, - order.data()); - - StyleValueVector axis_tags; - axis_tags.ensure_capacity(count); - for (size_t i = 0; i < count; ++i) - axis_tags.unchecked_append(values[order[i]]); - - return StyleValueList::create(move(axis_tags), StyleValueList::Separator::Comma); + return StyleValue::adopt_rust_style_value_data(static_cast( + ComputedValuesFFI::rust_compute_font_feature_settings(absolutized_value->rust_style_value_data()))); } NonnullRefPtr StyleComputer::compute_border_or_outline_width(NonnullRefPtr const& absolutized_value, double device_pixels_per_css_pixel) @@ -4579,49 +4645,6 @@ NonnullRefPtr StyleComputer::compute_line_height(NonnullRefPtr return NumberStyleValue::create(absolutized_value->as_calculated().resolve_number({ .percentage_basis = Length::make_px(computed_font_size) }).value()); } -// https://drafts.csswg.org/css-anchor-position/#position-area-computed -NonnullRefPtr StyleComputer::compute_position_area(NonnullRefPtr const& absolutized_value) -{ - // The computed value of a value is the two keywords indicating the selected tracks in each axis, - // with the long (block-start) and short (start) logical keywords treated as equivalent. It serializes in the order - // given in the grammar (above), with the logical keywords serialized in their short forms (e.g. start start - // instead of block-start inline-start). - if (absolutized_value->is_keyword()) - return absolutized_value; - - auto to_short_keyword = [](NonnullRefPtr const& keyword_value) -> NonnullRefPtr { - // The short-form mapping lives in the Rust style computation core. - auto short_keyword = static_cast(ComputedValuesFFI::rust_position_area_short_keyword(to_underlying(keyword_value->keyword()))); - if (short_keyword == keyword_value->keyword()) - return keyword_value; - return KeywordStyleValue::create(short_keyword); - }; - - auto const& value_list = absolutized_value->as_value_list(); - VERIFY(value_list.size() == 2); - - auto values = value_list.values(); - auto const& block_value = values.at(0); - auto const& inline_value = values.at(1); - - // When one axis is span-all, the value computes to a single logical keyword from the - // other axis. The remapping decision lives in the Rust style computation core. - auto span_all_remap = ComputedValuesFFI::rust_position_area_span_all_remap( - to_underlying(block_value->as_keyword().keyword()), to_underlying(inline_value->as_keyword().keyword())); - if (block_value->as_keyword().keyword() == Keyword::SpanAll || inline_value->as_keyword().keyword() == Keyword::SpanAll) { - if (span_all_remap.remapped) - return KeywordStyleValue::create(static_cast(span_all_remap.keyword)); - return absolutized_value; - } - - auto short_block_value = to_short_keyword(block_value->as_keyword()); - auto short_inline_value = to_short_keyword(inline_value->as_keyword()); - if (*block_value != short_block_value || *inline_value != short_inline_value) - return StyleValueList::create({ short_block_value, short_inline_value }, StyleValueList::Separator::Space); - - return absolutized_value; -} - // https://w3c.github.io/mathml-core/#propdef-math-depth NonnullRefPtr StyleComputer::compute_math_depth(NonnullRefPtr const& absolutized_value, Optional const& inheritance_parent) { diff --git a/Libraries/LibWeb/CSS/StyleComputer.h b/Libraries/LibWeb/CSS/StyleComputer.h index c94f2402135d5..dc01e9343c184 100644 --- a/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Libraries/LibWeb/CSS/StyleComputer.h @@ -134,6 +134,8 @@ class WEB_API StyleComputer final : public GC::Cell { void collect_animation_into(DOM::AbstractElement, GC::Ref animation, ComputedProperties&) const; void collect_animation_into(DOM::AbstractElement, GC::Ref animation, ComputedProperties::Builder&) const; + void collect_animations_into(DOM::AbstractElement, ReadonlySpan>, ComputedProperties&) const; + void collect_animations_into(DOM::AbstractElement, ReadonlySpan>, ComputedProperties::Builder&) const; [[nodiscard]] NonnullRefPtr compute_properties(DOM::AbstractElement, CascadedProperties&, u64 matching_pseudo_element_styles) const; @@ -156,7 +158,6 @@ class WEB_API StyleComputer final : public GC::Cell { static NonnullRefPtr compute_font_weight(NonnullRefPtr const& absolutized_value, Optional const& inheritance_parent); static NonnullRefPtr compute_font_width(NonnullRefPtr const& absolutized_value); static NonnullRefPtr compute_line_height(NonnullRefPtr const& absolutized_value, CSSPixels computed_font_size); - static NonnullRefPtr compute_position_area(NonnullRefPtr const& absolutized_value); [[nodiscard]] NonnullRefPtr build_computed_values(ComputedProperties&, DOM::AbstractElement, StyleScope const&) const; [[nodiscard]] NonnullRefPtr reconstruct_computed_properties(ComputedValues const&) const; @@ -195,7 +196,7 @@ class WEB_API StyleComputer final : public GC::Cell { [[nodiscard]] RefPtr compute_style_impl(DOM::AbstractElement, ComputeStyleMode, Optional did_change_custom_properties, StyleScope const&, IncludeInlineStyle) const; [[nodiscard]] NonnullRefPtr compute_cascaded_values(DOM::AbstractElement, MatchingRuleSet const&, IncludeInlineStyle) const; - void collect_animation_into(DOM::AbstractElement, GC::Ref animation, ComputedProperties&, ComputedProperties::Builder*) const; + void collect_animation_effects_into(DOM::AbstractElement, ReadonlySpan>, ComputedProperties&, ComputedProperties::Builder*) const; void compute_custom_properties(ComputedProperties&, DOM::AbstractElement) const; void start_needed_transitions(ComputedValues const& old_style, ComputedProperties::Builder& new_style, DOM::AbstractElement) const; void resolve_effective_overflow_values(ComputedProperties::Builder&) const; diff --git a/Libraries/LibWeb/CSS/StyleScope.cpp b/Libraries/LibWeb/CSS/StyleScope.cpp index 9533aba6fa1fd..3d99fbf26c5a7 100644 --- a/Libraries/LibWeb/CSS/StyleScope.cpp +++ b/Libraries/LibWeb/CSS/StyleScope.cpp @@ -1278,11 +1278,6 @@ bool StyleScope::may_have_has_selectors_with_relative_selector_that_has_sibling_ return rule_cache().selector_insights.has_has_selectors_with_relative_selector_that_has_sibling_combinator; } -bool StyleScope::have_size_container_queries() const -{ - return rule_cache().has_size_container_queries; -} - DOM::Document& StyleScope::document() const { return m_node->document(); diff --git a/Libraries/LibWeb/CSS/StyleScope.h b/Libraries/LibWeb/CSS/StyleScope.h index f93374db69592..b95b466b06fe2 100644 --- a/Libraries/LibWeb/CSS/StyleScope.h +++ b/Libraries/LibWeb/CSS/StyleScope.h @@ -219,7 +219,6 @@ class StyleScope { [[nodiscard]] bool may_have_user_has_selectors() const; [[nodiscard]] bool may_have_user_pseudo_class_selectors(PseudoClass) const; [[nodiscard]] bool may_have_has_selectors_with_relative_selector_that_has_sibling_combinator() const; - [[nodiscard]] bool have_size_container_queries() const; void for_each_active_css_style_sheet(Function const& callback) const; diff --git a/Libraries/LibWeb/CSS/StyleStructRef.h b/Libraries/LibWeb/CSS/StyleStructRef.h index 80921d360015c..2557d2fc8fe97 100644 --- a/Libraries/LibWeb/CSS/StyleStructRef.h +++ b/Libraries/LibWeb/CSS/StyleStructRef.h @@ -27,11 +27,12 @@ WEB_API void const* style_group_default_payload(size_t group_index); // A copy-on-write reference to a style value group struct. // // The payloads are owned by the Rust side of LibWeb (see computed_values.rs): -// Rust allocates, copies and destroys them through per-group vtable callbacks, -// and places an atomic reference count in a header immediately before each -// payload. Reading a group is an inline field access and sharing one is an -// inline atomic operation; only cloning for mutation and destroying the last -// reference cross the FFI boundary. +// Rust-native groups use their Rust layout and lifecycle directly, while groups +// containing C++ field types use registered lifecycle callbacks. Rust places an +// atomic reference count in a header immediately before each payload. Reading a +// group is an inline field access and sharing one is an inline atomic operation; +// only cloning for mutation and destroying the last reference cross the FFI +// boundary. // // Copying a StyleStructRef shares the underlying payload by bumping the // reference count instead of copying the struct. access() returns a mutable diff --git a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.cpp index b235cb4b8205a..be9b650281a22 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/AbstractImageStyleValue.cpp @@ -12,8 +12,11 @@ namespace Web::CSS { StyleValueFFI::RetainedColorStop retain_color_stop_for_rust(ColorStopListElement const& stop) { - return { { retain_style_value_for_rust(stop.transition_hint.ptr()) }, { retain_style_value_for_rust(stop.color_stop.color.ptr()) }, - { retain_style_value_for_rust(stop.color_stop.position.ptr()) }, { retain_style_value_for_rust(stop.color_stop.second_position.ptr()) } }; + auto retain = [](StyleValue const* value) { + return value ? StyleValueFFI::rust_style_value_retain(value->rust_style_value_data()) : nullptr; + }; + return { { retain(stop.transition_hint.ptr()) }, { retain(stop.color_stop.color.ptr()) }, + { retain(stop.color_stop.position.ptr()) }, { retain(stop.color_stop.second_position.ptr()) } }; } Vector retain_color_stops_for_rust(ReadonlySpan color_stop_list) @@ -27,12 +30,18 @@ Vector retain_color_stops_for_rust(ReadonlySpa ColorStopListElement color_stop_from_rust_data(StyleValueFFI::RetainedColorStop const& stop) { + auto adopt = [](auto const& retained) -> ValueComparingRefPtr { + auto const* data = static_cast(retained.pointer); + if (!data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(data)); + }; return { - .transition_hint = static_cast(stop.transition_hint.pointer), + .transition_hint = adopt(stop.transition_hint), .color_stop = { - .color = static_cast(stop.color.pointer), - .position = static_cast(stop.position.pointer), - .second_position = static_cast(stop.second_position.pointer), + .color = adopt(stop.color), + .position = adopt(stop.position), + .second_position = adopt(stop.second_position), }, }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.cpp index 38bb2e2e776a5..8507b4f2c4130 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.cpp @@ -10,17 +10,16 @@ namespace Web::CSS { -static StyleValueFFI::StyleValueData* make_anchor_size_data(Optional const& anchor_name, Optional const& anchor_size, ValueComparingRefPtr const& fallback_value) +static StyleValueFFI::StyleValueData const* make_anchor_size_data(Optional const& anchor_name, Optional const& anchor_size, ValueComparingRefPtr const& fallback_value) { - // The Rust allocation takes ownership of one strong reference to the fallback value. - if (fallback_value) - fallback_value->ref(); + // The Rust allocation takes ownership of one strong reference to the fallback value data. + auto const* fallback_data = fallback_value ? StyleValueFFI::rust_style_value_retain(fallback_value->rust_style_value_data()) : nullptr; return StyleValueFFI::rust_style_value_create_anchor_size( anchor_name.has_value(), anchor_name.has_value() ? anchor_name->to_raw_leaked() : 0, anchor_size.has_value(), anchor_size.has_value() ? to_underlying(*anchor_size) : 0, - fallback_value.ptr()); + fallback_data); } ValueComparingNonnullRefPtr AnchorSizeStyleValue::create( @@ -35,6 +34,7 @@ AnchorSizeStyleValue::AnchorSizeStyleValue( Optional const& anchor_size, ValueComparingRefPtr const& fallback_value) : StyleValueWithDefaultOperators(Type::AnchorSize, make_anchor_size_data(anchor_name, anchor_size, fallback_value)) + , m_fallback_value(fallback_value) { } diff --git a/Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.h index 3ef3da3210d39..eb74139c95a24 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/AnchorSizeStyleValue.h @@ -38,14 +38,26 @@ class AnchorSizeStyleValue final : public StyleValueWithDefaultOperators fallback_value() const { - return static_cast(m_value->anchor_size.fallback_value.pointer); + return m_fallback_value; } private: + friend class StyleValue; + + explicit AnchorSizeStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::AnchorSize, data) + { + auto const* fallback_data = static_cast(data->anchor_size.fallback_value.pointer); + if (fallback_data) + m_fallback_value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(fallback_data)); + } + AnchorSizeStyleValue( Optional const& anchor_name, Optional const& anchor_size, ValueComparingRefPtr const& fallback_value); + + ValueComparingRefPtr m_fallback_value; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/AnchorStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/AnchorStyleValue.cpp index 89aba42e2034e..e94f480965dac 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AnchorStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/AnchorStyleValue.cpp @@ -12,18 +12,16 @@ namespace Web::CSS { -static StyleValueFFI::StyleValueData* make_anchor_data(Optional const& anchor_name, ValueComparingNonnullRefPtr const& anchor_side, ValueComparingRefPtr const& fallback_value) +static StyleValueFFI::StyleValueData const* make_anchor_data(Optional const& anchor_name, ValueComparingNonnullRefPtr const& anchor_side, ValueComparingRefPtr const& fallback_value) { - // The Rust allocation takes ownership of one strong reference to the side and, when present, - // the fallback value. - anchor_side->ref(); - if (fallback_value) - fallback_value->ref(); + // The Rust allocation takes ownership of one strong reference to the side data and, when + // present, the fallback value data. + auto const* fallback_data = fallback_value ? StyleValueFFI::rust_style_value_retain(fallback_value->rust_style_value_data()) : nullptr; return StyleValueFFI::rust_style_value_create_anchor( anchor_name.has_value(), anchor_name.has_value() ? anchor_name->to_raw_leaked() : 0, - anchor_side.ptr(), - fallback_value.ptr()); + StyleValueFFI::rust_style_value_retain(anchor_side->rust_style_value_data()), + fallback_data); } ValueComparingNonnullRefPtr AnchorStyleValue::create( @@ -38,6 +36,8 @@ AnchorStyleValue::AnchorStyleValue(Optional const& anchor_name, ValueComparingNonnullRefPtr const& anchor_side, ValueComparingRefPtr const& fallback_value) : AbstractNonMathCalcFunctionStyleValue(Type::Anchor, make_anchor_data(anchor_name, anchor_side, fallback_value)) + , m_anchor_side(anchor_side) + , m_fallback_value(fallback_value) { } diff --git a/Libraries/LibWeb/CSS/StyleValues/AnchorStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/AnchorStyleValue.h index 4e96f013239a5..6e0b85d220a1d 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AnchorStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/AnchorStyleValue.h @@ -33,15 +33,29 @@ class AnchorStyleValue final : public AbstractNonMathCalcFunctionStyleValue { } ValueComparingNonnullRefPtr anchor_side() const { - return *static_cast(m_value->anchor.anchor_side.pointer); + return m_anchor_side; } ValueComparingRefPtr fallback_value() const { - return static_cast(m_value->anchor.fallback_value.pointer); + return m_fallback_value; } private: + friend class StyleValue; + + explicit AnchorStyleValue(StyleValueFFI::StyleValueData const* data) + : AbstractNonMathCalcFunctionStyleValue(Type::Anchor, data) + , m_anchor_side(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->anchor.anchor_side.pointer)))) + { + auto const* fallback_data = static_cast(data->anchor.fallback_value.pointer); + if (fallback_data) + m_fallback_value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(fallback_data)); + } + AnchorStyleValue(Optional const& anchor_name, ValueComparingNonnullRefPtr const& anchor_side, ValueComparingRefPtr const& fallback_value); + + ValueComparingNonnullRefPtr m_anchor_side; + ValueComparingRefPtr m_fallback_value; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/AngleStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/AngleStyleValue.h index 8dda2ea2c595f..3820577957518 100644 --- a/Libraries/LibWeb/CSS/StyleValues/AngleStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/AngleStyleValue.h @@ -33,6 +33,13 @@ class AngleStyleValue : public DimensionStyleValue { bool equals(StyleValue const& other) const; private: + friend class StyleValue; + + explicit AngleStyleValue(StyleValueFFI::StyleValueData const* data) + : DimensionStyleValue(Type::Angle, data) + { + } + explicit AngleStyleValue(Angle angle); }; diff --git a/Libraries/LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.cpp index c9842fadab645..652329a54ffea 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.cpp @@ -12,7 +12,9 @@ namespace Web::CSS { BackgroundSizeStyleValue::BackgroundSizeStyleValue(ValueComparingNonnullRefPtr size_x, ValueComparingNonnullRefPtr size_y) - : StyleValueWithDefaultOperators(Type::BackgroundSize, StyleValueFFI::rust_style_value_create_background_size(&size_x.leak_ref(), &size_y.leak_ref())) + : StyleValueWithDefaultOperators(Type::BackgroundSize, StyleValueFFI::rust_style_value_create_background_size(StyleValueFFI::rust_style_value_retain(size_x->rust_style_value_data()), StyleValueFFI::rust_style_value_retain(size_y->rust_style_value_data()))) + , m_size_x(move(size_x)) + , m_size_y(move(size_y)) { } diff --git a/Libraries/LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h index 40afc6ff04014..35c9fb31043d1 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/BackgroundSizeStyleValue.h @@ -24,8 +24,8 @@ class BackgroundSizeStyleValue final : public StyleValueWithDefaultOperators size_x() const { return *static_cast(m_value->background_size.size_x.pointer); } - ValueComparingNonnullRefPtr size_y() const { return *static_cast(m_value->background_size.size_y.pointer); } + ValueComparingNonnullRefPtr size_x() const { return m_size_x; } + ValueComparingNonnullRefPtr size_y() const { return m_size_y; } void serialize(StringBuilder&, SerializationMode) const; ValueComparingNonnullRefPtr absolutized(ComputationContext const&) const; @@ -33,7 +33,19 @@ class BackgroundSizeStyleValue final : public StyleValueWithDefaultOperators(data->background_size.size_x.pointer)))) + , m_size_y(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->background_size.size_y.pointer)))) + { + } + BackgroundSizeStyleValue(ValueComparingNonnullRefPtr size_x, ValueComparingNonnullRefPtr size_y); + + ValueComparingNonnullRefPtr m_size_x; + ValueComparingNonnullRefPtr m_size_y; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp index 456837a196593..1122560506d7b 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.cpp @@ -20,30 +20,32 @@ namespace Web::CSS { -StyleValueFFI::StyleValueData* BasicShapeStyleValue::make_basic_shape_data(BasicShape const& basic_shape) +StyleValueFFI::StyleValueData const* BasicShapeStyleValue::make_basic_shape_data(BasicShape const& basic_shape) { - // The Rust allocation takes ownership of one strong reference to each non-null value. + auto retain = [](StyleValue const* value) { + return value ? StyleValueFFI::rust_style_value_retain(value->rust_style_value_data()) : nullptr; + }; return basic_shape.visit( - [](Inset const& inset) { - return StyleValueFFI::rust_style_value_create_basic_shape(0, retain_style_value_for_rust(inset.top.ptr()), retain_style_value_for_rust(inset.right.ptr()), retain_style_value_for_rust(inset.bottom.ptr()), retain_style_value_for_rust(inset.left.ptr()), retain_style_value_for_rust(inset.border_radius.ptr()), 0, nullptr, 0, 0); + [&](Inset const& inset) { + return StyleValueFFI::rust_style_value_create_basic_shape(0, retain(inset.top.ptr()), retain(inset.right.ptr()), retain(inset.bottom.ptr()), retain(inset.left.ptr()), retain(inset.border_radius.ptr()), 0, nullptr, 0, 0); }, - [](Xywh const& xywh) { - return StyleValueFFI::rust_style_value_create_basic_shape(1, retain_style_value_for_rust(xywh.x.ptr()), retain_style_value_for_rust(xywh.y.ptr()), retain_style_value_for_rust(xywh.width.ptr()), retain_style_value_for_rust(xywh.height.ptr()), retain_style_value_for_rust(xywh.border_radius.ptr()), 0, nullptr, 0, 0); + [&](Xywh const& xywh) { + return StyleValueFFI::rust_style_value_create_basic_shape(1, retain(xywh.x.ptr()), retain(xywh.y.ptr()), retain(xywh.width.ptr()), retain(xywh.height.ptr()), retain(xywh.border_radius.ptr()), 0, nullptr, 0, 0); }, - [](Rect const& rect) { - return StyleValueFFI::rust_style_value_create_basic_shape(2, retain_style_value_for_rust(rect.top.ptr()), retain_style_value_for_rust(rect.right.ptr()), retain_style_value_for_rust(rect.bottom.ptr()), retain_style_value_for_rust(rect.left.ptr()), retain_style_value_for_rust(rect.border_radius.ptr()), 0, nullptr, 0, 0); + [&](Rect const& rect) { + return StyleValueFFI::rust_style_value_create_basic_shape(2, retain(rect.top.ptr()), retain(rect.right.ptr()), retain(rect.bottom.ptr()), retain(rect.left.ptr()), retain(rect.border_radius.ptr()), 0, nullptr, 0, 0); }, - [](Circle const& circle) { - return StyleValueFFI::rust_style_value_create_basic_shape(3, retain_style_value_for_rust(circle.radius.ptr()), retain_style_value_for_rust(circle.position.ptr()), nullptr, nullptr, nullptr, 0, nullptr, 0, 0); + [&](Circle const& circle) { + return StyleValueFFI::rust_style_value_create_basic_shape(3, retain(circle.radius.ptr()), retain(circle.position.ptr()), nullptr, nullptr, nullptr, 0, nullptr, 0, 0); }, - [](Ellipse const& ellipse) { - return StyleValueFFI::rust_style_value_create_basic_shape(4, retain_style_value_for_rust(ellipse.radius.ptr()), retain_style_value_for_rust(ellipse.position.ptr()), nullptr, nullptr, nullptr, 0, nullptr, 0, 0); + [&](Ellipse const& ellipse) { + return StyleValueFFI::rust_style_value_create_basic_shape(4, retain(ellipse.radius.ptr()), retain(ellipse.position.ptr()), nullptr, nullptr, nullptr, 0, nullptr, 0, 0); }, - [](Polygon const& polygon) { + [&](Polygon const& polygon) { Vector points; points.ensure_capacity(polygon.points.size()); for (auto const& point : polygon.points) - points.unchecked_append({ { retain_style_value_for_rust(point.x.ptr()) }, { retain_style_value_for_rust(point.y.ptr()) } }); + points.unchecked_append({ { retain(point.x.ptr()) }, { retain(point.y.ptr()) } }); return StyleValueFFI::rust_style_value_create_basic_shape(5, nullptr, nullptr, nullptr, nullptr, nullptr, static_cast(to_underlying(polygon.fill_rule)), points.data(), points.size(), 0); }, [](Path const& path) { @@ -51,6 +53,49 @@ StyleValueFFI::StyleValueData* BasicShapeStyleValue::make_basic_shape_data(Basic }); } +BasicShapeStyleValue::BasicShapeStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::BasicShape, data) + , m_shape([&]() -> BasicShape { + auto adopt = [](auto const& retained) -> ValueComparingNonnullRefPtr { + auto const* child_data = static_cast(retained.pointer); + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(child_data)); + }; + auto adopt_optional = [](auto const& retained) -> ValueComparingRefPtr { + auto const* child_data = static_cast(retained.pointer); + if (!child_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(child_data)); + }; + auto const& shape = data->basic_shape; + switch (shape.kind) { + case 0: + return Inset { adopt(shape.v0), adopt(shape.v1), adopt(shape.v2), adopt(shape.v3), adopt(shape.v4) }; + case 1: + return Xywh { adopt(shape.v0), adopt(shape.v1), adopt(shape.v2), adopt(shape.v3), adopt(shape.v4) }; + case 2: + return Rect { adopt(shape.v0), adopt(shape.v1), adopt(shape.v2), adopt(shape.v3), adopt(shape.v4) }; + case 3: + return Circle { adopt(shape.v0), adopt_optional(shape.v1) }; + case 4: + return Ellipse { adopt(shape.v0), adopt_optional(shape.v1) }; + case 5: { + Vector points; + points.ensure_capacity(shape.points.length); + for (size_t i = 0; i < shape.points.length; ++i) + points.unchecked_append({ adopt(shape.points.pointer[i].x), adopt(shape.points.pointer[i].y) }); + return Polygon { static_cast(shape.fill_rule), move(points) }; + } + case 6: { + auto path_string = Utf16String::from_raw(shape.path_string.raw); + return Path { static_cast(shape.fill_rule), SVG::AttributeParser::parse_path_data(path_string) }; + } + default: + VERIFY_NOT_REACHED(); + } + }()) +{ +} + BasicShape const& BasicShapeStyleValue::basic_shape() const { return m_shape; diff --git a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h index 14edd302d44d0..44e51c1e31faa 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/BasicShapeStyleValue.h @@ -123,13 +123,17 @@ class BasicShapeStyleValue : public StyleValueWithDefaultOperators top() const { return *static_cast(m_value->border_image_slice.top.pointer); } - ValueComparingNonnullRefPtr left() const { return *static_cast(m_value->border_image_slice.left.pointer); } - ValueComparingNonnullRefPtr bottom() const { return *static_cast(m_value->border_image_slice.bottom.pointer); } - ValueComparingNonnullRefPtr right() const { return *static_cast(m_value->border_image_slice.right.pointer); } + ValueComparingNonnullRefPtr top() const { return m_top; } + ValueComparingNonnullRefPtr left() const { return m_left; } + ValueComparingNonnullRefPtr bottom() const { return m_bottom; } + ValueComparingNonnullRefPtr right() const { return m_right; } bool fill() const { return m_value->border_image_slice.fill; } @@ -31,10 +31,30 @@ class BorderImageSliceStyleValue final : public StyleValueWithDefaultOperators(data->border_image_slice.top.pointer)))) + , m_right(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->border_image_slice.right.pointer)))) + , m_bottom(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->border_image_slice.bottom.pointer)))) + , m_left(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->border_image_slice.left.pointer)))) + { + } + BorderImageSliceStyleValue(ValueComparingNonnullRefPtr top, ValueComparingNonnullRefPtr right, ValueComparingNonnullRefPtr bottom, ValueComparingNonnullRefPtr left, bool fill) - : StyleValueWithDefaultOperators(Type::BorderImageSlice, StyleValueFFI::rust_style_value_create_border_image_slice(&top.leak_ref(), &right.leak_ref(), &bottom.leak_ref(), &left.leak_ref(), fill)) + : StyleValueWithDefaultOperators(Type::BorderImageSlice, StyleValueFFI::rust_style_value_create_border_image_slice(StyleValueFFI::rust_style_value_retain(top->rust_style_value_data()), StyleValueFFI::rust_style_value_retain(right->rust_style_value_data()), StyleValueFFI::rust_style_value_retain(bottom->rust_style_value_data()), StyleValueFFI::rust_style_value_retain(left->rust_style_value_data()), fill)) + , m_top(move(top)) + , m_right(move(right)) + , m_bottom(move(bottom)) + , m_left(move(left)) { } + + ValueComparingNonnullRefPtr m_top; + ValueComparingNonnullRefPtr m_right; + ValueComparingNonnullRefPtr m_bottom; + ValueComparingNonnullRefPtr m_left; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/BorderRadiusRectStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/BorderRadiusRectStyleValue.h index 0664bf0c830b3..b0ab23e70ef24 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BorderRadiusRectStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/BorderRadiusRectStyleValue.h @@ -29,10 +29,10 @@ class BorderRadiusRectStyleValue final : public StyleValueWithDefaultOperators absolutized(ComputationContext const&) const; - ValueComparingNonnullRefPtr top_left() const { return *static_cast(m_value->border_radius_rect.top_left.pointer); } - ValueComparingNonnullRefPtr top_right() const { return *static_cast(m_value->border_radius_rect.top_right.pointer); } - ValueComparingNonnullRefPtr bottom_right() const { return *static_cast(m_value->border_radius_rect.bottom_right.pointer); } - ValueComparingNonnullRefPtr bottom_left() const { return *static_cast(m_value->border_radius_rect.bottom_left.pointer); } + ValueComparingNonnullRefPtr top_left() const { return m_top_left; } + ValueComparingNonnullRefPtr top_right() const { return m_top_right; } + ValueComparingNonnullRefPtr bottom_right() const { return m_bottom_right; } + ValueComparingNonnullRefPtr bottom_left() const { return m_bottom_left; } bool properties_equal(BorderRadiusRectStyleValue const& other) const { @@ -43,10 +43,30 @@ class BorderRadiusRectStyleValue final : public StyleValueWithDefaultOperators(data->border_radius_rect.top_left.pointer)))) + , m_top_right(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->border_radius_rect.top_right.pointer)))) + , m_bottom_right(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->border_radius_rect.bottom_right.pointer)))) + , m_bottom_left(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->border_radius_rect.bottom_left.pointer)))) + { + } + BorderRadiusRectStyleValue(NonnullRefPtr top_left, NonnullRefPtr top_right, NonnullRefPtr bottom_right, NonnullRefPtr bottom_left) - : StyleValueWithDefaultOperators(Type::BorderRadiusRect, StyleValueFFI::rust_style_value_create_border_radius_rect(&top_left.leak_ref(), &top_right.leak_ref(), &bottom_right.leak_ref(), &bottom_left.leak_ref())) + : StyleValueWithDefaultOperators(Type::BorderRadiusRect, StyleValueFFI::rust_style_value_create_border_radius_rect(StyleValueFFI::rust_style_value_retain(top_left->rust_style_value_data()), StyleValueFFI::rust_style_value_retain(top_right->rust_style_value_data()), StyleValueFFI::rust_style_value_retain(bottom_right->rust_style_value_data()), StyleValueFFI::rust_style_value_retain(bottom_left->rust_style_value_data()))) + , m_top_left(move(top_left)) + , m_top_right(move(top_right)) + , m_bottom_right(move(bottom_right)) + , m_bottom_left(move(bottom_left)) { } + + ValueComparingNonnullRefPtr m_top_left; + ValueComparingNonnullRefPtr m_top_right; + ValueComparingNonnullRefPtr m_bottom_right; + ValueComparingNonnullRefPtr m_bottom_left; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h index 9cc9c1955def7..8d94cbf99e0ae 100644 --- a/Libraries/LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/BorderRadiusStyleValue.h @@ -28,8 +28,8 @@ class BorderRadiusStyleValue final : public StyleValueWithDefaultOperators horizontal_radius() const { return *static_cast(m_value->border_radius.horizontal_radius.pointer); } - ValueComparingNonnullRefPtr vertical_radius() const { return *static_cast(m_value->border_radius.vertical_radius.pointer); } + ValueComparingNonnullRefPtr horizontal_radius() const { return m_horizontal_radius; } + ValueComparingNonnullRefPtr vertical_radius() const { return m_vertical_radius; } bool is_elliptical() const { return m_value->border_radius.is_elliptical; } void serialize(StringBuilder&, SerializationMode) const; @@ -42,22 +42,26 @@ class BorderRadiusStyleValue final : public StyleValueWithDefaultOperators const& horizontal_radius, ValueComparingNonnullRefPtr const& vertical_radius) - : StyleValueWithDefaultOperators(Type::BorderRadius, make_border_radius_data(horizontal_radius, vertical_radius)) + explicit BorderRadiusStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::BorderRadius, data) + , m_horizontal_radius(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->border_radius.horizontal_radius.pointer)))) + , m_vertical_radius(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->border_radius.vertical_radius.pointer)))) { } - static StyleValueFFI::StyleValueData* make_border_radius_data(ValueComparingNonnullRefPtr const& horizontal_radius, ValueComparingNonnullRefPtr const& vertical_radius) + BorderRadiusStyleValue(ValueComparingNonnullRefPtr const& horizontal_radius, ValueComparingNonnullRefPtr const& vertical_radius) + : StyleValueWithDefaultOperators(Type::BorderRadius, StyleValueFFI::rust_style_value_create_border_radius(horizontal_radius != vertical_radius, StyleValueFFI::rust_style_value_retain(horizontal_radius->rust_style_value_data()), StyleValueFFI::rust_style_value_retain(vertical_radius->rust_style_value_data()))) + , m_horizontal_radius(horizontal_radius) + , m_vertical_radius(vertical_radius) { - // The Rust allocation takes ownership of one strong reference to each radius. - return StyleValueFFI::rust_style_value_create_border_radius( - horizontal_radius != vertical_radius, - retain_style_value_for_rust(horizontal_radius.ptr()), retain_style_value_for_rust(vertical_radius.ptr())); } // NB: StyleValue dispatches operations by type tag, so it may call private impls. friend class StyleValue; ValueComparingNonnullRefPtr absolutized(ComputationContext const&) const; + + ValueComparingNonnullRefPtr m_horizontal_radius; + ValueComparingNonnullRefPtr m_vertical_radius; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp index 46df51a4ba0be..c3e6f256d7901 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.cpp @@ -38,6 +38,12 @@ namespace Web::CSS { +static ValueComparingNonnullRefPtr wrap_borrowed_style_value_data(void const* data) +{ + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(data))); +} + // Marshals a numeric type into its FFI mirror. static StyleValueFFI::FfiNumericType to_ffi_numeric_type(Optional const& type) { @@ -56,12 +62,12 @@ static StyleValueFFI::FfiNumericType to_ffi_numeric_type(Optional c return result; } +static Optional from_ffi_numeric_type(StyleValueFFI::FfiNumericType const&); + // Builds the Rust mirror of a calculation tree, transferring ownership of the // returned handle to the caller. The child orders follow each node's members. -StyleValueFFI::StyleValueData* CalculatedStyleValue::make_calculated_data_from_rust_root(StyleValueFFI::CalcNode const* rust_root, NumericType const& resolved_type, CalculationContext const& context) +StyleValueFFI::StyleValueData const* CalculatedStyleValue::make_calculated_data_from_rust_root(StyleValueFFI::CalcNode const* rust_root, NumericType const& resolved_type, CalculationContext const& context) { - static_assert(IsTriviallyCopyable); - auto resolved_type_bytes = bit_cast>(resolved_type); Vector ranges; ranges.ensure_capacity(context.accepted_ranges_by_type.size()); for (auto const& [value_type, range] : context.accepted_ranges_by_type) @@ -71,7 +77,7 @@ StyleValueFFI::StyleValueData* CalculatedStyleValue::make_calculated_data_from_r : OptionalNone {}; return StyleValueFFI::rust_style_value_create_calculated( rust_root, - resolved_type_bytes.data(), resolved_type_bytes.size(), + to_ffi_numeric_type(resolved_type), context.percentages_resolve_as.has_value(), context.percentages_resolve_as == ValueType::Number, resolve_as_base.has_value() ? to_underlying(*resolve_as_base) : 0, @@ -87,11 +93,7 @@ ValueComparingNonnullRefPtr CalculatedStyleValue::cr NumericType CalculatedStyleValue::resolved_type() const { - auto const& blob = m_value->calculated.resolved_type; - Array bytes; - VERIFY(blob.length == bytes.size()); - __builtin_memcpy(bytes.data(), blob.pointer, bytes.size()); - return bit_cast(bytes); + return from_ffi_numeric_type(m_value->calculated.resolved_type).release_value(); } CalculationContext CalculatedStyleValue::calculation_context() const @@ -121,58 +123,63 @@ CalculationContext CalculationContext::for_property(PropertyNameAndID const& pro // https://drafts.csswg.org/css-values-4/#funcdef-min void CalculatedStyleValue::serialize(StringBuilder& builder, SerializationMode mode) const { - // The serialization structure lives in the Rust style computation core; every formatted - // byte still comes from the value serializers below. Trees containing random() keep the - // C++ path, like resolution does. - struct SerializationCallbackContext { - StringBuilder& builder; - SerializationMode mode; - } callback_context { builder, mode }; - - StyleValueFFI::FfiCalcSerializationCallbacks const callbacks { - .context = &callback_context, - .append_literal = [](void* context, u8 const* bytes, size_t length) { - auto& callback_context = *static_cast(context); - callback_context.builder.append(StringView { bytes, length }); }, - .append_numeric_leaf = [](void* context, u8 kind, double value, u8 unit, bool) { - auto& callback_context = *static_cast(context); - switch (kind) { + struct Serialization { + StyleValueFFI::FfiCalcSerialization pieces; + ~Serialization() { StyleValueFFI::rust_calc_serialization_release(pieces.storage); } + } serialization { StyleValueFFI::rust_calc_serialize(m_value.operator->(), mode == SerializationMode::ResolvedValue) }; + + bool previous_piece_appended = false; + for (auto const& piece : ReadonlySpan { serialization.pieces.pieces, serialization.pieces.piece_count }) { + auto start_length = builder.length(); + switch (piece.kind) { + case 0: + builder.append(StringView { piece.bytes, piece.length }); + break; + case 1: + switch (piece.numeric_kind) { case 0: - Number { static_cast(unit), value }.serialize(callback_context.builder, callback_context.mode); - return; + Number { static_cast(piece.unit_or_channel), piece.value }.serialize(builder, mode); + break; case 1: - Angle { value, static_cast(unit) }.serialize(callback_context.builder, callback_context.mode); - return; + Angle { piece.value, static_cast(piece.unit_or_channel) }.serialize(builder, mode); + break; case 2: - Flex { value, static_cast(unit) }.serialize(callback_context.builder, callback_context.mode); - return; + Flex { piece.value, static_cast(piece.unit_or_channel) }.serialize(builder, mode); + break; case 3: - Frequency { value, static_cast(unit) }.serialize(callback_context.builder, callback_context.mode); - return; + Frequency { piece.value, static_cast(piece.unit_or_channel) }.serialize(builder, mode); + break; case 4: - Length { value, static_cast(unit) }.serialize(callback_context.builder, callback_context.mode); - return; + Length { piece.value, static_cast(piece.unit_or_channel) }.serialize(builder, mode); + break; case 5: - Percentage { value }.serialize(callback_context.builder, callback_context.mode); - return; + Percentage { piece.value }.serialize(builder, mode); + break; case 6: - Resolution { value, static_cast(unit) }.serialize(callback_context.builder, callback_context.mode); - return; + Resolution { piece.value, static_cast(piece.unit_or_channel) }.serialize(builder, mode); + break; case 7: - Time { value, static_cast(unit) }.serialize(callback_context.builder, callback_context.mode); - return; + Time { piece.value, static_cast(piece.unit_or_channel) }.serialize(builder, mode); + break; + default: + VERIFY_NOT_REACHED(); } - VERIFY_NOT_REACHED(); }, - .append_style_value = [](void* context, void const* shell) -> bool { - auto& callback_context = *static_cast(context); - auto start_length = callback_context.builder.length(); - static_cast(shell)->serialize(callback_context.builder, callback_context.mode); - return callback_context.builder.length() > start_length; }, - .append_channel_name = [](void* context, u8 channel) { - auto& callback_context = *static_cast(context); - callback_context.builder.append(CSS::to_string(static_cast(channel))); }, - }; - StyleValueFFI::rust_calc_serialize(m_value.operator->(), &callbacks, mode == SerializationMode::ResolvedValue); + break; + case 2: + wrap_borrowed_style_value_data(piece.style_value)->serialize(builder, mode); + break; + case 3: + builder.append(CSS::to_string(static_cast(piece.unit_or_channel))); + break; + case 4: + if (previous_piece_appended) + builder.append(StringView { piece.bytes, piece.length }); + break; + default: + VERIFY_NOT_REACHED(); + } + previous_piece_appended = builder.length() > start_length; + } } // The RoundingStrategy discriminants cross the boundary as round()'s strategy code; pin them. @@ -211,99 +218,90 @@ static Optional from_ffi_numeric_type(StyleValueFFI::FfiNumericType return result; } -// The callback seams shared by resolution and absolutization: the C++ leaf -// I/O the Rust calc core calls back into while simplifying a tree. -struct CalcResolveCallbackContext { - CalculationContext const& calculation_context; - CalculationResolutionContext const& resolution_context; -}; - -static StyleValueFFI::FfiCalcResolutionContext make_calc_ffi_resolution_context(CalcResolveCallbackContext& callback_context, Optional& length_context_storage) -{ - StyleValueFFI::FfiCalcResolutionContext ffi_context { - .basis_kind = 0, - .basis_value = 0, - .basis_unit = 0, - .length_resolution_context = nullptr, - .callback_context = &callback_context, - .resolve_non_math_function = [](void* context, void const* shell) -> StyleValueFFI::CalcNode const* { - auto& callback_context = *static_cast(context); - auto resolved = static_cast(shell)->resolve_to_calculation_node(callback_context.calculation_context, callback_context.resolution_context); - if (!resolved.has_value()) - return nullptr; - return resolved->release(); - }, - .resolve_channel_keyword = [](void* context, u8 channel, double* out_value) -> bool { - auto& callback_context = *static_cast(context); - if (!callback_context.resolution_context.relative_color.has_value()) - return false; - auto resolved = callback_context.resolution_context.relative_color->get(static_cast(channel)); - if (!resolved.has_value()) - return false; - *out_value = resolved.value(); - return true; - }, - .random_base_value = [](void* context, void const* sharing, double* out_value) -> bool { - auto& callback_context = *static_cast(context); - // NB: We don't want to resolve this before computation time even if it's possible. - auto const& resolution_context = callback_context.resolution_context; - if (!resolution_context.abstract_element.has_value() && !resolution_context.length_resolution_context.has_value() && resolution_context.percentage_basis.has()) - return false; - *out_value = static_cast(sharing)->random_base_value(); - return true; - }, - .absolutize_random_sharing = [](void* context, void const* sharing) -> void const* { - auto& callback_context = *static_cast(context); - auto const& resolution_context = callback_context.resolution_context; - // When we are in the absolutization process we should absolutize the sharing options. - if (!resolution_context.length_resolution_context.has_value()) - return nullptr; - ComputationContext computation_context { - .length_resolution_context = resolution_context.length_resolution_context.value(), - .abstract_element = resolution_context.abstract_element - }; - auto absolutized = static_cast(sharing)->absolutized(computation_context); - return retain_style_value_for_rust(absolutized.ptr()); - }, - .resolve_length = [](void* context, double value, u8 unit, double* out_px) -> bool { - auto& callback_context = *static_cast(context); - auto const& resolution_context = callback_context.resolution_context; - if (!resolution_context.length_resolution_context.has_value()) - return false; - *out_px = Length { value, static_cast(unit) }.to_px(*resolution_context.length_resolution_context).to_double(); - return true; - }, - }; - auto const& resolution_context = callback_context.resolution_context; - if (resolution_context.length_resolution_context.has_value()) { - length_context_storage = to_ffi_length_resolution_context(*resolution_context.length_resolution_context); - ffi_context.length_resolution_context = &length_context_storage.value(); +struct CalcResolutionSnapshot { + CalcResolutionSnapshot(StyleValueFFI::CalcNode const* root, CalculationContext const& calculation_context, CalculationResolutionContext const& resolution_context) + { + if (resolution_context.length_resolution_context.has_value()) { + length_resolution_context = to_ffi_length_resolution_context(*resolution_context.length_resolution_context); + ffi_context.length_resolution_context = &length_resolution_context.value(); + } + resolution_context.percentage_basis.visit( + [](Empty const&) {}, + [&](Angle const& angle) { + ffi_context.basis_kind = 1; + ffi_context.basis_value = angle.raw_value(); + ffi_context.basis_unit = to_underlying(angle.unit()); + }, + [&](Frequency const& frequency) { + ffi_context.basis_kind = 2; + ffi_context.basis_value = frequency.raw_value(); + ffi_context.basis_unit = to_underlying(frequency.unit()); + }, + [&](Length const& length) { + ffi_context.basis_kind = 3; + ffi_context.basis_value = length.raw_value(); + ffi_context.basis_unit = to_underlying(length.unit()); + }, + [&](Time const& time) { + ffi_context.basis_kind = 4; + ffi_context.basis_value = time.raw_value(); + ffi_context.basis_unit = to_underlying(time.unit()); + }); + + external_resolutions = StyleValueFFI::rust_calc_external_resolutions(root, ffi_context.basis_kind, ffi_context.basis_value, ffi_context.basis_unit); + ffi_context.external_resolutions = external_resolutions.resolutions; + ffi_context.external_resolution_count = external_resolutions.resolution_count; + for (auto& resolution : Span { external_resolutions.resolutions, external_resolutions.resolution_count }) { + switch (resolution.kind) { + case StyleValueFFI::FfiCalcExternalResolutionKind::NonMathFunction: { + auto function = wrap_borrowed_style_value_data(resolution.source); + auto resolved = static_cast(*function).resolve_to_calculation_node(calculation_context, resolution_context); + if (resolved.has_value()) + resolution.resolved_node = resolved->release(); + break; + } + case StyleValueFFI::FfiCalcExternalResolutionKind::Channel: + if (resolution_context.relative_color.has_value()) { + if (auto value = resolution_context.relative_color->get(static_cast(resolution.unit_or_channel)); value.has_value()) { + resolution.has_number = true; + resolution.number = value.value(); + } + } + break; + case StyleValueFFI::FfiCalcExternalResolutionKind::RandomSharing: { + auto sharing = wrap_borrowed_style_value_data(resolution.source); + // When we are in the absolutization process we should absolutize the sharing options. + if (resolution_context.length_resolution_context.has_value()) { + ComputationContext context { resolution_context.length_resolution_context.value(), resolution_context.abstract_element }; + auto absolutized = sharing->as_random_value_sharing().absolutized(context); + resolution.resolved_style_value = StyleValueFFI::rust_style_value_retain(absolutized->rust_style_value_data()); + resolution.has_number = true; + resolution.number = absolutized->as_random_value_sharing().random_base_value(); + } else if (resolution_context.abstract_element.has_value() || !resolution_context.percentage_basis.has()) { + // NB: We don't want to resolve this before computation time even if it's possible. + resolution.has_number = true; + resolution.number = sharing->as_random_value_sharing().random_base_value(); + } + break; + } + case StyleValueFFI::FfiCalcExternalResolutionKind::Length: + if (resolution_context.length_resolution_context.has_value()) { + resolution.has_number = true; + resolution.number = Length { resolution.input_value, static_cast(resolution.unit_or_channel) }.to_px(*resolution_context.length_resolution_context).to_double(); + } + break; + default: + VERIFY_NOT_REACHED(); + } + } } - resolution_context.percentage_basis.visit( - [](Empty const&) {}, - [&](Angle const& angle) { - ffi_context.basis_kind = 1; - ffi_context.basis_value = angle.raw_value(); - ffi_context.basis_unit = to_underlying(angle.unit()); - }, - [&](Frequency const& frequency) { - ffi_context.basis_kind = 2; - ffi_context.basis_value = frequency.raw_value(); - ffi_context.basis_unit = to_underlying(frequency.unit()); - }, - [&](Length const& length) { - ffi_context.basis_kind = 3; - ffi_context.basis_value = length.raw_value(); - ffi_context.basis_unit = to_underlying(length.unit()); - }, - [&](Time const& time) { - ffi_context.basis_kind = 4; - ffi_context.basis_value = time.raw_value(); - ffi_context.basis_unit = to_underlying(time.unit()); - }); - return ffi_context; -} + ~CalcResolutionSnapshot() { StyleValueFFI::rust_calc_external_resolutions_release(external_resolutions.storage); } + + Optional length_resolution_context; + StyleValueFFI::FfiCalcExternalResolutions external_resolutions {}; + StyleValueFFI::FfiCalcResolutionContext ffi_context {}; +}; ValueComparingNonnullRefPtr CalculatedStyleValue::absolutized(ComputationContext const& computation_context) const { @@ -311,11 +309,9 @@ ValueComparingNonnullRefPtr CalculatedStyleValue::absolutized( auto calculation_context = this->calculation_context(); auto resolution_context = CalculationResolutionContext::from_computation_context(computation_context); - CalcResolveCallbackContext callback_context { calculation_context, resolution_context }; - Optional ffi_length_resolution_context; - auto ffi_context = make_calc_ffi_resolution_context(callback_context, ffi_length_resolution_context); + CalcResolutionSnapshot resolution_snapshot { m_value->calculated.rust_calculation.node, calculation_context, resolution_context }; - auto result = StyleValueFFI::rust_calc_absolutize(m_value.operator->(), &ffi_context); + auto result = StyleValueFFI::rust_calc_absolutize(m_value.operator->(), &resolution_snapshot.ffi_context); if (result.is_percentage) return PercentageStyleValue::create(Percentage { result.percentage_value }); @@ -329,13 +325,9 @@ bool CalculatedStyleValue::equals(StyleValue const& other) const if (type() != other.type()) return false; - // Structural equality runs over the Rust trees; the style values carried by - // random() and non-math-function nodes compare through their own equals. - return StyleValueFFI::rust_calc_equals( - m_value.operator->(), other.as_calculated().m_value.operator->(), nullptr, - [](void*, void const* a, void const* b) -> bool { - return static_cast(a)->equals(*static_cast(b)); - }); + // NB: Structural equality runs entirely over the Rust value graph, including the style + // values retained by random() and non-math-function nodes. + return StyleValueFFI::rust_calc_equals(m_value.operator->(), other.as_calculated().m_value.operator->()); } // https://drafts.csswg.org/css-values-4/#calc-computed-value @@ -347,11 +339,9 @@ Optional CalculatedStyleValue::resolve_valu Optional CalculatedStyleValue::resolve_value(CalculationContext const& calculation_context, CalculationResolutionContext const& resolution_context, bool apply_censoring_and_clamping) const { // The resolution runs in the Rust style computation core. - CalcResolveCallbackContext callback_context { calculation_context, resolution_context }; - Optional ffi_length_resolution_context; - auto ffi_context = make_calc_ffi_resolution_context(callback_context, ffi_length_resolution_context); + CalcResolutionSnapshot resolution_snapshot { m_value->calculated.rust_calculation.node, calculation_context, resolution_context }; - auto rust_result = StyleValueFFI::rust_calc_resolve(m_value.operator->(), &ffi_context, apply_censoring_and_clamping); + auto rust_result = StyleValueFFI::rust_calc_resolve(m_value.operator->(), &resolution_snapshot.ffi_context, apply_censoring_and_clamping); if (!rust_result.resolved) return {}; return ResolvedValue { rust_result.value, from_ffi_numeric_type(rust_result.numeric_type) }; @@ -496,136 +486,111 @@ bool CalculatedStyleValue::is_fully_simplified() const return resolve_value({}).has_value(); } -// Reifies one node of the Rust calculation tree into its typed-om object. -static GC::Ptr reify_rust_calc_node(JS::Realm& realm, void const* calculated_data, StyleValueFFI::CalcNode const* node) +// https://drafts.css-houdini.org/css-typed-om-1/#reify-a-math-expression +static GC::Ptr reify_rust_calculation(JS::Realm& realm, void const* calculated_data) { - auto numeric_type_of = [&]() { - return from_ffi_numeric_type(StyleValueFFI::rust_calc_node_numeric_type(calculated_data, node)).value(); - }; - auto children_of = [&]() { - Vector children; - auto count = StyleValueFFI::rust_calc_node_children(node, nullptr, 0); - children.resize(count); - StyleValueFFI::rust_calc_node_children(node, children.data(), children.size()); - return children; - }; - auto reify_children_of = [&]() -> GC::Ptr { - GC::RootVector> reified_children; - for (auto const* child : children_of()) { - auto reified_child = reify_rust_calc_node(realm, calculated_data, child); - if (!reified_child) - return nullptr; - reified_children.append(reified_child.as_nonnull()); - } - return CSSNumericArray::create(realm, move(reified_children)); - }; + struct Reification { + StyleValueFFI::FfiCalcReification description; + ~Reification() { StyleValueFFI::rust_calc_reification_release(description.storage); } + } reification { StyleValueFFI::rust_calc_describe_for_typed_om(calculated_data) }; + if (reification.description.node_count == 0) + return nullptr; + + auto nodes = ReadonlySpan { reification.description.nodes, reification.description.node_count }; + auto child_indices = ReadonlySpan { reification.description.children, reification.description.child_count }; + GC::RootVector> reified_nodes; + reified_nodes.ensure_capacity(nodes.size()); + for (auto const& node : nodes) { + auto numeric_type = from_ffi_numeric_type(node.numeric_type).release_value(); + VERIFY(node.child_start + node.child_count <= child_indices.size()); + auto children = child_indices.slice(node.child_start, node.child_count); + auto child = [&](size_t index) -> GC::Ref { + VERIFY(index < children.size()); + VERIFY(children[index] < reified_nodes.size()); + return reified_nodes[children[index]]; + }; + auto reify_children = [&]() { + GC::RootVector> result; + result.ensure_capacity(children.size()); + for (auto index : children) { + VERIFY(index < reified_nodes.size()); + result.append(reified_nodes[index]); + } + return CSSNumericArray::create(realm, move(result)); + }; - switch (StyleValueFFI::rust_calc_node_kind(node)) { - case 0: { - // A numeric leaf reifies as a unit value in its own unit. - u8 kind = 0; - double value = 0; - u8 unit = 0; - StyleValueFFI::rust_calc_node_numeric_leaf(node, &kind, &value, &unit); - switch (kind) { + switch (node.kind) { case 0: - return CSSUnitValue::create(realm, value, "number"_utf16_fly_string); - case 1: - return CSSUnitValue::create(realm, value, Angle { value, static_cast(unit) }.unit_name()); + switch (node.numeric_kind) { + case 0: + reified_nodes.append(CSSUnitValue::create(realm, node.value, "number"_utf16_fly_string)); + break; + case 1: + reified_nodes.append(CSSUnitValue::create(realm, node.value, Angle { node.value, static_cast(node.unit) }.unit_name())); + break; + case 2: + reified_nodes.append(CSSUnitValue::create(realm, node.value, Flex { node.value, static_cast(node.unit) }.unit_name())); + break; + case 3: + reified_nodes.append(CSSUnitValue::create(realm, node.value, Frequency { node.value, static_cast(node.unit) }.unit_name())); + break; + case 4: + reified_nodes.append(CSSUnitValue::create(realm, node.value, Length { node.value, static_cast(node.unit) }.unit_name())); + break; + case 5: + reified_nodes.append(CSSUnitValue::create(realm, node.value, "percent"_utf16_fly_string)); + break; + case 6: + reified_nodes.append(CSSUnitValue::create(realm, node.value, Resolution { node.value, static_cast(node.unit) }.unit_name())); + break; + case 7: + reified_nodes.append(CSSUnitValue::create(realm, node.value, Time { node.value, static_cast(node.unit) }.unit_name())); + break; + default: + VERIFY_NOT_REACHED(); + } + break; case 2: - return CSSUnitValue::create(realm, value, Flex { value, static_cast(unit) }.unit_name()); + reified_nodes.append(CSSMathSum::create(realm, move(numeric_type), reify_children())); + break; case 3: - return CSSUnitValue::create(realm, value, Frequency { value, static_cast(unit) }.unit_name()); + reified_nodes.append(CSSMathProduct::create(realm, move(numeric_type), reify_children())); + break; case 4: - return CSSUnitValue::create(realm, value, Length { value, static_cast(unit) }.unit_name()); + VERIFY(children.size() == 1); + reified_nodes.append(CSSMathNegate::create(realm, move(numeric_type), child(0))); + break; case 5: - return CSSUnitValue::create(realm, value, "percent"_utf16_fly_string); + VERIFY(children.size() == 1); + reified_nodes.append(CSSMathInvert::create(realm, move(numeric_type), child(0))); + break; case 6: - return CSSUnitValue::create(realm, value, Resolution { value, static_cast(unit) }.unit_name()); + reified_nodes.append(CSSMathMin::create(realm, move(numeric_type), reify_children())); + break; case 7: - return CSSUnitValue::create(realm, value, Time { value, static_cast(unit) }.unit_name()); + reified_nodes.append(CSSMathMax::create(realm, move(numeric_type), reify_children())); + break; + case 8: + VERIFY(children.size() == 3); + reified_nodes.append(CSSMathClamp::create(realm, move(numeric_type), child(0), child(1), child(2))); + break; + default: + VERIFY_NOT_REACHED(); } - VERIFY_NOT_REACHED(); - } - case 2: { - auto reified_children = reify_children_of(); - if (!reified_children) - return nullptr; - return CSSMathSum::create(realm, numeric_type_of(), reified_children.as_nonnull()); - } - case 3: { - auto reified_children = reify_children_of(); - if (!reified_children) - return nullptr; - return CSSMathProduct::create(realm, numeric_type_of(), reified_children.as_nonnull()); - } - case 4: - case 5: { - auto children = children_of(); - VERIFY(children.size() == 1); - auto reified_child = reify_rust_calc_node(realm, calculated_data, children[0]); - if (!reified_child) - return nullptr; - if (StyleValueFFI::rust_calc_node_kind(node) == 4) - return CSSMathNegate::create(realm, numeric_type_of(), reified_child.as_nonnull()); - return CSSMathInvert::create(realm, numeric_type_of(), reified_child.as_nonnull()); - } - case 6: { - auto reified_children = reify_children_of(); - if (!reified_children) - return nullptr; - return CSSMathMin::create(realm, numeric_type_of(), reified_children.as_nonnull()); - } - case 7: { - auto reified_children = reify_children_of(); - if (!reified_children) - return nullptr; - return CSSMathMax::create(realm, numeric_type_of(), reified_children.as_nonnull()); - } - case 8: { - auto children = children_of(); - VERIFY(children.size() == 3); - auto lower = reify_rust_calc_node(realm, calculated_data, children[0]); - auto value = reify_rust_calc_node(realm, calculated_data, children[1]); - auto upper = reify_rust_calc_node(realm, calculated_data, children[2]); - if (!lower || !value || !upper) - return nullptr; - return CSSMathClamp::create(realm, numeric_type_of(), lower.as_nonnull(), value.as_nonnull(), upper.as_nonnull()); - } - default: - // Some math functions are not reifiable yet. - // https://github.com/w3c/css-houdini-drafts/issues/1090 - return nullptr; - } -} - -// https://drafts.css-houdini.org/css-typed-om-1/#reify-a-math-expression -static bool rust_calc_node_contains_anchor(StyleValueFFI::CalcNode const* node) -{ - if (StyleValueFFI::rust_calc_node_kind(node) == 28 - && static_cast(StyleValueFFI::rust_calc_node_style_value(node))->is_anchor()) - return true; - Vector children; - auto count = StyleValueFFI::rust_calc_node_children(node, nullptr, 0); - children.resize(count); - StyleValueFFI::rust_calc_node_children(node, children.data(), children.size()); - for (auto const* child : children) { - if (rust_calc_node_contains_anchor(child)) - return true; } - return false; + return reified_nodes.last(); } bool CalculatedStyleValue::contains_anchor_function() const { - return rust_calc_node_contains_anchor(rust_calculation_root()); + return StyleValueFFI::rust_calc_contains_anchor(m_value.operator->()); } GC::Ref CalculatedStyleValue::reify(JS::Realm& realm, Utf16FlyString const& associated_property) const { - // NB: This spec algorithm isn't really implementable here - it's incomplete, and assumes we don't already have a - // calculation tree. So we have a per-node method instead, walking the Rust tree. - if (auto reified = reify_rust_calc_node(realm, m_value.operator->(), m_value->calculated.rust_calculation.node)) + // NB: This spec algorithm is incomplete and assumes we do not already have a calculation tree. + // Rust describes the existing tree in one batch instead. + if (auto reified = reify_rust_calculation(realm, m_value.operator->())) return *reified; // Some math functions are not reifiable yet. If we contain one, we have to fall back to CSSStyleValue. // https://github.com/w3c/css-houdini-drafts/issues/1090 @@ -745,14 +710,14 @@ CalcNodeRef CalcNodeRef::random(StyleValue const& random_value_sharing, CalcNode return adopt(StyleValueFFI::rust_calc_node_create_random( minimum_handle, maximum_handle, step.has_value() ? step->release() : nullptr, - retain_style_value_for_rust(&random_value_sharing))); + StyleValueFFI::rust_style_value_retain(random_value_sharing.rust_style_value_data()))); } CalcNodeRef CalcNodeRef::non_math_function(StyleValue const& function, Optional const& numeric_type) { auto ffi_numeric_type = to_ffi_numeric_type(numeric_type); return adopt(StyleValueFFI::rust_calc_node_create_non_math_function( - retain_style_value_for_rust(&function), &ffi_numeric_type)); + StyleValueFFI::rust_style_value_retain(function.rust_style_value_data()), &ffi_numeric_type)); } CalcNodeRef CalcNodeRef::from_style_value(StyleValue const& style_value) @@ -794,9 +759,7 @@ Optional CalcNodeRef::determine_type(CalculationContext const& cont // https://drafts.csswg.org/css-values-4/#calc-simplification CalcNodeRef simplify_a_calculation_tree(CalcNodeRef const& root, CalculationContext const& context, CalculationResolutionContext const& resolution_context) { - CalcResolveCallbackContext callback_context { context, resolution_context }; - Optional ffi_length_resolution_context; - auto ffi_context = make_calc_ffi_resolution_context(callback_context, ffi_length_resolution_context); + CalcResolutionSnapshot resolution_snapshot { root.node(), context, resolution_context }; auto resolve_as_base = context.percentages_resolve_as.has_value() ? NumericType::base_type_from_value_type(*context.percentages_resolve_as) @@ -804,7 +767,7 @@ CalcNodeRef simplify_a_calculation_tree(CalcNodeRef const& root, CalculationCont return CalcNodeRef::adopt(StyleValueFFI::rust_calc_simplify_tree( root.node(), - &ffi_context, + &resolution_snapshot.ffi_context, context.percentages_resolve_as.has_value(), context.percentages_resolve_as == ValueType::Number, resolve_as_base.has_value() ? to_underlying(*resolve_as_base) : 0)); diff --git a/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h index 2484c390dac9b..16570148bad52 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CalculatedStyleValue.h @@ -90,6 +90,13 @@ class CalculatedStyleValue : public StyleValue { bool contains_anchor_function() const; private: + friend class StyleValue; + + explicit CalculatedStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValue(Type::Calculated, data) + { + } + // Takes ownership of a transferred Rust calculation root. explicit CalculatedStyleValue(StyleValueFFI::CalcNode const* rust_root, NumericType resolved_type, CalculationContext context) : StyleValue(Type::Calculated, make_calculated_data_from_rust_root(rust_root, resolved_type, context)) @@ -107,7 +114,7 @@ class CalculatedStyleValue : public StyleValue { Optional resolve_value(CalculationResolutionContext const&, bool apply_censoring_and_clamping = true) const; Optional resolve_value(CalculationContext const&, CalculationResolutionContext const&, bool apply_censoring_and_clamping = true) const; - static StyleValueFFI::StyleValueData* make_calculated_data_from_rust_root(StyleValueFFI::CalcNode const*, NumericType const&, CalculationContext const&); + static StyleValueFFI::StyleValueData const* make_calculated_data_from_rust_root(StyleValueFFI::CalcNode const*, NumericType const&, CalculationContext const&); NumericType resolved_type() const; }; diff --git a/Libraries/LibWeb/CSS/StyleValues/ColorFunctionStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ColorFunctionStyleValue.cpp index 5e46c96fa8860..77c9d1e32c900 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ColorFunctionStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ColorFunctionStyleValue.cpp @@ -18,6 +18,38 @@ namespace Web::CSS { +ColorFunctionStyleValue::ColorFunctionStyleValue(StyleValueFFI::StyleValueData const* data) + : ColorStyleValue(data) + , m_channels([&] { + auto adopt = [](auto const& retained) -> ValueComparingNonnullRefPtr { + auto const* child_data = static_cast(retained.pointer); + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(child_data)); + }; + auto const& color = data->color_function; + return Array, 3> { + adopt(color.channel_0), adopt(color.channel_1), adopt(color.channel_2) + }; + }()) + , m_alpha([&]() -> ValueComparingRefPtr { + auto const* child_data = static_cast(data->color_function.alpha.pointer); + if (!child_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(child_data)); + }()) + , m_name([&]() -> Optional { + if (!data->color_function.has_name) + return {}; + return Utf16FlyString::from_raw(data->color_function.name.raw); + }()) + , m_origin_color([&]() -> ValueComparingRefPtr { + auto const* child_data = static_cast(data->color_function.origin_color.pointer); + if (!child_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(child_data)); + }()) +{ +} + ValueComparingNonnullRefPtr ColorFunctionStyleValue::create( ColorType color_type, ValueComparingNonnullRefPtr c1, diff --git a/Libraries/LibWeb/CSS/StyleValues/ColorFunctionStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ColorFunctionStyleValue.h index 5b7cfe014a4f5..adc1d685dfd4f 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ColorFunctionStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ColorFunctionStyleValue.h @@ -33,34 +33,15 @@ class WEB_API ColorFunctionStyleValue final : public ColorStyleValue { StyleValue const& channel(size_t index) const { - auto const& data = m_value->color_function; - switch (index) { - case 0: - return *static_cast(data.channel_0.pointer); - case 1: - return *static_cast(data.channel_1.pointer); - case 2: - return *static_cast(data.channel_2.pointer); - } - VERIFY_NOT_REACHED(); + return *m_channels.at(index); } Array, 3> channels() const { - auto const& data = m_value->color_function; - return { - ValueComparingNonnullRefPtr { *static_cast(data.channel_0.pointer) }, - ValueComparingNonnullRefPtr { *static_cast(data.channel_1.pointer) }, - ValueComparingNonnullRefPtr { *static_cast(data.channel_2.pointer) }, - }; + return m_channels; } - ValueComparingRefPtr alpha() const { return static_cast(m_value->color_function.alpha.pointer); } - Optional name() const - { - if (!m_value->color_function.has_name) - return {}; - return Utf16FlyString::from_raw(m_value->color_function.name.raw); - } - ValueComparingRefPtr origin_color() const { return static_cast(m_value->color_function.origin_color.pointer); } + ValueComparingRefPtr alpha() const { return m_alpha; } + Optional name() const { return m_name; } + ValueComparingRefPtr origin_color() const { return m_origin_color; } ColorFunctionDescriptor const& descriptor() const { return color_function_descriptor_for(*color_type()); } @@ -79,6 +60,8 @@ class WEB_API ColorFunctionStyleValue final : public ColorStyleValue { } private: + friend class StyleValue; + ColorFunctionStyleValue( ColorType color_type, ValueComparingNonnullRefPtr c1, @@ -89,22 +72,32 @@ class WEB_API ColorFunctionStyleValue final : public ColorStyleValue { Optional name, ValueComparingRefPtr origin_color) : ColorStyleValue(make_color_function_data(color_type, color_syntax, c1, c2, c3, alpha, name, origin_color)) + , m_channels { move(c1), move(c2), move(c3) } + , m_alpha(move(alpha)) + , m_name(move(name)) + , m_origin_color(move(origin_color)) { } - static StyleValueFFI::StyleValueData* make_color_function_data(Optional color_type, ColorSyntax color_syntax, NonnullRefPtr const& c1, NonnullRefPtr const& c2, NonnullRefPtr const& c3, RefPtr const& alpha, Optional const& name, RefPtr const& origin_color) + explicit ColorFunctionStyleValue(StyleValueFFI::StyleValueData const*); + + static StyleValueFFI::StyleValueData const* make_color_function_data(Optional color_type, ColorSyntax color_syntax, NonnullRefPtr const& c1, NonnullRefPtr const& c2, NonnullRefPtr const& c3, RefPtr const& alpha, Optional const& name, RefPtr const& origin_color) { - // The Rust allocation takes ownership of one strong reference to each non-null value - // and one leaked reference to the name when present. - c1->ref(); - c2->ref(); - c3->ref(); - if (alpha) - alpha->ref(); - if (origin_color) - origin_color->ref(); - return StyleValueFFI::rust_style_value_create_color_function(color_type.has_value(), color_type_byte(color_type), to_underlying(color_syntax), c1.ptr(), c2.ptr(), c3.ptr(), alpha.ptr(), name.has_value(), name.has_value() ? name->to_raw_leaked() : 0, origin_color.ptr()); + auto const* alpha_data = alpha ? StyleValueFFI::rust_style_value_retain(alpha->rust_style_value_data()) : nullptr; + auto const* origin_color_data = origin_color ? StyleValueFFI::rust_style_value_retain(origin_color->rust_style_value_data()) : nullptr; + return StyleValueFFI::rust_style_value_create_color_function( + color_type.has_value(), color_type_byte(color_type), to_underlying(color_syntax), + StyleValueFFI::rust_style_value_retain(c1->rust_style_value_data()), + StyleValueFFI::rust_style_value_retain(c2->rust_style_value_data()), + StyleValueFFI::rust_style_value_retain(c3->rust_style_value_data()), + alpha_data, name.has_value(), name.has_value() ? name->to_raw_leaked() : 0, + origin_color_data); } + + Array, 3> m_channels; + ValueComparingRefPtr m_alpha; + Optional m_name; + ValueComparingRefPtr m_origin_color; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/ColorInterpolationMethodStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ColorInterpolationMethodStyleValue.h index b5ac581344860..62bf1b16906f8 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ColorInterpolationMethodStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ColorInterpolationMethodStyleValue.h @@ -44,12 +44,19 @@ class ColorInterpolationMethodStyleValue final : public StyleValueWithDefaultOpe bool properties_equal(ColorInterpolationMethodStyleValue const& other) const { return color_interpolation_method() == other.color_interpolation_method(); } private: + friend class StyleValue; + explicit ColorInterpolationMethodStyleValue(ColorInterpolationMethod color_space) : StyleValueWithDefaultOperators(Type::ColorInterpolationMethod, make_color_interpolation_method_data(color_space)) { } - static StyleValueFFI::StyleValueData* make_color_interpolation_method_data(ColorInterpolationMethod const& color_interpolation_method) + explicit ColorInterpolationMethodStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::ColorInterpolationMethod, data) + { + } + + static StyleValueFFI::StyleValueData const* make_color_interpolation_method_data(ColorInterpolationMethod const& color_interpolation_method) { return color_interpolation_method.visit( [](RectangularColorSpace const& color_space) { diff --git a/Libraries/LibWeb/CSS/StyleValues/ColorMixStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ColorMixStyleValue.cpp index 012537917ded0..4ac0166904cb0 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ColorMixStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ColorMixStyleValue.cpp @@ -6,15 +6,213 @@ */ #include "ColorMixStyleValue.h" +#include #include -#include -#include +#include #include #include #include namespace Web::CSS { +// The Rust color conversion and interpolation code mirrors these enum values across FFI. +static_assert(to_underlying(ColorStyleValue::ColorType::RGB) == 0); +static_assert(to_underlying(ColorStyleValue::ColorType::A98RGB) == 1); +static_assert(to_underlying(ColorStyleValue::ColorType::DisplayP3) == 2); +static_assert(to_underlying(ColorStyleValue::ColorType::DisplayP3Linear) == 3); +static_assert(to_underlying(ColorStyleValue::ColorType::HSL) == 4); +static_assert(to_underlying(ColorStyleValue::ColorType::HWB) == 5); +static_assert(to_underlying(ColorStyleValue::ColorType::Lab) == 6); +static_assert(to_underlying(ColorStyleValue::ColorType::LCH) == 7); +static_assert(to_underlying(ColorStyleValue::ColorType::OKLab) == 8); +static_assert(to_underlying(ColorStyleValue::ColorType::OKLCH) == 9); +static_assert(to_underlying(ColorStyleValue::ColorType::sRGB) == 10); +static_assert(to_underlying(ColorStyleValue::ColorType::sRGBLinear) == 11); +static_assert(to_underlying(ColorStyleValue::ColorType::ProPhotoRGB) == 12); +static_assert(to_underlying(ColorStyleValue::ColorType::Rec2020) == 13); +static_assert(to_underlying(ColorStyleValue::ColorType::XYZD50) == 14); +static_assert(to_underlying(ColorStyleValue::ColorType::XYZD65) == 15); +static_assert(to_underlying(Gfx::RectangularColorSpace::Srgb) == 0); +static_assert(to_underlying(Gfx::RectangularColorSpace::SrgbLinear) == 1); +static_assert(to_underlying(Gfx::RectangularColorSpace::DisplayP3) == 2); +static_assert(to_underlying(Gfx::RectangularColorSpace::DisplayP3Linear) == 3); +static_assert(to_underlying(Gfx::RectangularColorSpace::A98Rgb) == 4); +static_assert(to_underlying(Gfx::RectangularColorSpace::ProphotoRgb) == 5); +static_assert(to_underlying(Gfx::RectangularColorSpace::Rec2020) == 6); +static_assert(to_underlying(Gfx::RectangularColorSpace::Lab) == 7); +static_assert(to_underlying(Gfx::RectangularColorSpace::Oklab) == 8); +static_assert(to_underlying(Gfx::RectangularColorSpace::Xyz) == 9); +static_assert(to_underlying(Gfx::RectangularColorSpace::XyzD50) == 10); +static_assert(to_underlying(Gfx::RectangularColorSpace::XyzD65) == 11); +static_assert(to_underlying(Gfx::PolarColorSpace::Hsl) == 0); +static_assert(to_underlying(Gfx::PolarColorSpace::Hwb) == 1); +static_assert(to_underlying(Gfx::PolarColorSpace::Lch) == 2); +static_assert(to_underlying(Gfx::PolarColorSpace::Oklch) == 3); +static_assert(to_underlying(Gfx::HueInterpolationMethod::Shorter) == 0); +static_assert(to_underlying(Gfx::HueInterpolationMethod::Longer) == 1); +static_assert(to_underlying(Gfx::HueInterpolationMethod::Increasing) == 2); +static_assert(to_underlying(Gfx::HueInterpolationMethod::Decreasing) == 3); + +static bool is_missing_color_component(StyleValue const& component) +{ + return component.to_keyword() == Keyword::None; +} + +static Optional resolve_native_color_components(StyleValue const& style_value, CalculationResolutionContext const& context) +{ + if (!style_value.is_color_function()) + return {}; + + auto const& color = as(style_value); + auto color_type = color.color_type(); + if (!color_type.has_value() || color.origin_color()) + return {}; + + auto resolve_alpha = [&](ValueComparingRefPtr const& alpha_style_value) -> Optional { + // An omitted alpha on a ColorFunctionStyleValue is treated as 1 for interpolation. + if (!alpha_style_value) + return 1.0f; + auto result = ColorStyleValue::resolve_alpha(*alpha_style_value, context); + if (!result.has_value()) + return {}; + return static_cast(result.value()); + }; + + switch (*color_type) { + case ColorStyleValue::ColorType::HSL: { + auto h = ColorStyleValue::resolve_hue(color.channel(0), context); + auto s = ColorStyleValue::resolve_with_reference_value(color.channel(1), 100.0f, context); + auto l = ColorStyleValue::resolve_with_reference_value(color.channel(2), 100.0f, context); + auto a = resolve_alpha(color.alpha()); + if (!h.has_value() || !s.has_value() || !l.has_value() || !a.has_value()) + return {}; + return Gfx::ColorComponents { static_cast(h.value()), static_cast(s.value() / 100.0), static_cast(l.value() / 100.0), a.value() }; + } + case ColorStyleValue::ColorType::HWB: { + auto h = ColorStyleValue::resolve_hue(color.channel(0), context); + auto w = ColorStyleValue::resolve_with_reference_value(color.channel(1), 100.0f, context); + auto b = ColorStyleValue::resolve_with_reference_value(color.channel(2), 100.0f, context); + auto a = resolve_alpha(color.alpha()); + if (!h.has_value() || !w.has_value() || !b.has_value() || !a.has_value()) + return {}; + return Gfx::ColorComponents { static_cast(h.value()), static_cast(w.value() / 100.0), static_cast(b.value() / 100.0), a.value() }; + } + case ColorStyleValue::ColorType::Lab: { + auto l = ColorStyleValue::resolve_with_reference_value(color.channel(0), 100.0f, context); + auto a_component = ColorStyleValue::resolve_with_reference_value(color.channel(1), 125.0f, context); + auto b_component = ColorStyleValue::resolve_with_reference_value(color.channel(2), 125.0f, context); + auto alpha = resolve_alpha(color.alpha()); + if (!l.has_value() || !a_component.has_value() || !b_component.has_value() || !alpha.has_value()) + return {}; + return Gfx::ColorComponents { static_cast(l.value()), static_cast(a_component.value()), static_cast(b_component.value()), alpha.value() }; + } + case ColorStyleValue::ColorType::OKLab: { + auto l = ColorStyleValue::resolve_with_reference_value(color.channel(0), 1.0f, context); + auto a_component = ColorStyleValue::resolve_with_reference_value(color.channel(1), 0.4f, context); + auto b_component = ColorStyleValue::resolve_with_reference_value(color.channel(2), 0.4f, context); + auto alpha = resolve_alpha(color.alpha()); + if (!l.has_value() || !a_component.has_value() || !b_component.has_value() || !alpha.has_value()) + return {}; + return Gfx::ColorComponents { static_cast(l.value()), static_cast(a_component.value()), static_cast(b_component.value()), alpha.value() }; + } + case ColorStyleValue::ColorType::LCH: { + auto l = ColorStyleValue::resolve_with_reference_value(color.channel(0), 100.0f, context); + auto c = ColorStyleValue::resolve_with_reference_value(color.channel(1), 150.0f, context); + auto h = ColorStyleValue::resolve_hue(color.channel(2), context); + auto a = resolve_alpha(color.alpha()); + if (!l.has_value() || !c.has_value() || !h.has_value() || !a.has_value()) + return {}; + return Gfx::ColorComponents { static_cast(l.value()), static_cast(c.value()), static_cast(h.value()), a.value() }; + } + case ColorStyleValue::ColorType::OKLCH: { + auto l = ColorStyleValue::resolve_with_reference_value(color.channel(0), 1.0f, context); + auto c = ColorStyleValue::resolve_with_reference_value(color.channel(1), 0.4f, context); + auto h = ColorStyleValue::resolve_hue(color.channel(2), context); + auto a = resolve_alpha(color.alpha()); + if (!l.has_value() || !c.has_value() || !h.has_value() || !a.has_value()) + return {}; + return Gfx::ColorComponents { static_cast(l.value()), static_cast(c.value()), static_cast(h.value()), a.value() }; + } + case ColorStyleValue::ColorType::RGB: { + auto r = ColorStyleValue::resolve_with_reference_value(color.channel(0), 255.0f, context); + auto g = ColorStyleValue::resolve_with_reference_value(color.channel(1), 255.0f, context); + auto b = ColorStyleValue::resolve_with_reference_value(color.channel(2), 255.0f, context); + auto a = resolve_alpha(color.alpha()); + if (!r.has_value() || !g.has_value() || !b.has_value() || !a.has_value()) + return {}; + return Gfx::ColorComponents { + static_cast(clamp(r.value(), 0.0, 255.0) / 255.0), + static_cast(clamp(g.value(), 0.0, 255.0) / 255.0), + static_cast(clamp(b.value(), 0.0, 255.0) / 255.0), + a.value(), + }; + } + default: { + auto first = ColorStyleValue::resolve_with_reference_value(color.channel(0), 1.0f, context); + auto second = ColorStyleValue::resolve_with_reference_value(color.channel(1), 1.0f, context); + auto third = ColorStyleValue::resolve_with_reference_value(color.channel(2), 1.0f, context); + auto alpha = resolve_alpha(color.alpha()); + if (!first.has_value() || !second.has_value() || !third.has_value() || !alpha.has_value()) + return {}; + return Gfx::ColorComponents { static_cast(first.value()), static_cast(second.value()), static_cast(third.value()), alpha.value() }; + } + } +} + +static Optional resolve_color_for_rust_interpolation(StyleValue const& input, ColorResolutionContext const& context) +{ + RefPtr resolved_relative_color; + auto const* style_value = &input; + if (input.is_color_function()) { + auto const& color_function = as(input); + if (color_function.origin_color()) { + resolved_relative_color = color_function.resolve_relative_form(context); + if (!resolved_relative_color) + return {}; + style_value = resolved_relative_color.ptr(); + } + } + + StyleValueFFI::FfiResolvedColor result {}; + if (auto native_components = resolve_native_color_components(*style_value, context.calculation_resolution_context); native_components.has_value()) { + auto const& color_function = as(*style_value); + result.color_type = to_underlying(color_function.color_type().value()); + result.components[0] = (*native_components)[0]; + result.components[1] = (*native_components)[1]; + result.components[2] = (*native_components)[2]; + result.components[3] = native_components->alpha(); + result.missing[0] = is_missing_color_component(color_function.channel(0)); + result.missing[1] = is_missing_color_component(color_function.channel(1)); + result.missing[2] = is_missing_color_component(color_function.channel(2)); + result.missing[3] = color_function.alpha() && is_missing_color_component(*color_function.alpha()); + result.has_native_components = true; + return result; + } + + auto resolved_color = style_value->to_color(context); + if (!resolved_color.has_value()) + return {}; + auto components = Gfx::color_to_srgb(*resolved_color); + result.color_type = to_underlying(ColorStyleValue::ColorType::sRGB); + result.components[0] = components[0]; + result.components[1] = components[1]; + result.components[2] = components[2]; + result.components[3] = components.alpha(); + return result; +} + +static RefPtr interpolate_color_in_rust(StyleValue const& from, StyleValue const& to, float delta, double alpha_multiplier, StyleValue const& color_interpolation_method, ColorResolutionContext const& context) +{ + auto resolved_from = resolve_color_for_rust_interpolation(from, context); + auto resolved_to = resolve_color_for_rust_interpolation(to, context); + if (!resolved_from.has_value() || !resolved_to.has_value()) + return {}; + auto const* result = StyleValueFFI::rust_interpolate_color(&*resolved_from, &*resolved_to, color_interpolation_method.rust_style_value_data(), delta, static_cast(alpha_multiplier)); + if (!result) + return {}; + return StyleValue::adopt_rust_style_value_data(result); +} + ValueComparingNonnullRefPtr ColorMixStyleValue::create(RefPtr color_interpolation_method, ColorMixComponent first_component, ColorMixComponent second_component) { return adopt_ref(*new (nothrow) ColorMixStyleValue(move(color_interpolation_method), move(first_component), move(second_component))); @@ -22,6 +220,36 @@ ValueComparingNonnullRefPtr ColorMixStyleValue::create ColorMixStyleValue::ColorMixStyleValue(RefPtr color_interpolation_method, ColorMixComponent first_component, ColorMixComponent second_component) : ColorStyleValue(make_color_mix_data(color_interpolation_method, first_component, second_component)) + , m_color_interpolation_method(move(color_interpolation_method)) + , m_first_component(move(first_component)) + , m_second_component(move(second_component)) +{ +} + +ColorMixStyleValue::ColorMixStyleValue(StyleValueFFI::StyleValueData const* data) + : ColorStyleValue(data) + , m_color_interpolation_method([&]() -> ValueComparingRefPtr { + auto const* child_data = static_cast(data->color_mix.color_interpolation_method.pointer); + if (!child_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(child_data)); + }()) + , m_first_component([&] { + auto const& color_mix = data->color_mix; + auto color = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(color_mix.first_color.pointer))); + ValueComparingRefPtr percentage; + if (auto const* percentage_data = static_cast(color_mix.first_percentage.pointer)) + percentage = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(percentage_data)); + return ColorMixComponent { move(color), move(percentage) }; + }()) + , m_second_component([&] { + auto const& color_mix = data->color_mix; + auto color = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(color_mix.second_color.pointer))); + ValueComparingRefPtr percentage; + if (auto const* percentage_data = static_cast(color_mix.second_percentage.pointer)) + percentage = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(percentage_data)); + return ColorMixComponent { move(color), move(percentage) }; + }()) { } @@ -198,21 +426,19 @@ Optional ColorMixStyleValue::to_color(ColorResolutionContext color_resolu : Optional {}; auto normalized = normalize_percentage_pair(p1, p2); - auto color_interpolation_method = color_interpolation_method_value() - ? color_interpolation_method_value()->as_color_interpolation_method().color_interpolation_method() - : ColorInterpolationMethodStyleValue::ColorInterpolationMethod { RectangularColorSpace::Oklab }; - - auto interpolated = perform_color_interpolation(*first_component().color, *second_component().color, normalized.second_percentage.as_fraction(), color_interpolation_method, color_resolution_context); - if (!interpolated.has_value()) - return {}; - - if (normalized.alpha_multiplier < 1.0) - interpolated->components.set_alpha(interpolated->components.alpha() * normalized.alpha_multiplier); - - auto style_value = style_value_for_interpolated_color(*interpolated); + auto default_color_interpolation_method = ColorInterpolationMethodStyleValue::create(RectangularColorSpace::Oklab); + auto const& color_interpolation_method = color_interpolation_method_value() + ? *color_interpolation_method_value() + : static_cast(*default_color_interpolation_method); + auto style_value = interpolate_color_in_rust( + *first_component().color, + *second_component().color, + normalized.second_percentage.as_fraction(), + normalized.alpha_multiplier, + color_interpolation_method, + color_resolution_context); if (!style_value) return {}; - return style_value->to_color(color_resolution_context); } @@ -231,9 +457,10 @@ ValueComparingNonnullRefPtr ColorMixStyleValue::absolutized(Co auto delta = Percentage::from_style_value(normalized_percentages.p2).as_fraction(); - auto color_interpolation_method = absolutized_color_interpolation_method - ? absolutized_color_interpolation_method->as_color_interpolation_method().color_interpolation_method() - : ColorInterpolationMethodStyleValue::ColorInterpolationMethod { RectangularColorSpace::Oklab }; + auto default_color_interpolation_method = ColorInterpolationMethodStyleValue::create(RectangularColorSpace::Oklab); + auto const& color_interpolation_method = absolutized_color_interpolation_method + ? *absolutized_color_interpolation_method + : static_cast(*default_color_interpolation_method); // Resolve relative-color components before interpolation so channel-keyword references and `none` missing-channel // markers are made visible during interpolation. @@ -251,12 +478,14 @@ ValueComparingNonnullRefPtr ColorMixStyleValue::absolutized(Co auto resolved_first_color = resolve_if_relative(this->first_component().color); auto resolved_second_color = resolve_if_relative(this->second_component().color); - if (auto interpolated = perform_color_interpolation(*resolved_first_color, *resolved_second_color, delta, color_interpolation_method, color_resolution_context); interpolated.has_value()) { - if (normalized_percentages.alpha_multiplier < 1.0) - interpolated->components.set_alpha(interpolated->components.alpha() * normalized_percentages.alpha_multiplier); - if (auto style_value = style_value_for_interpolated_color(*interpolated)) - return style_value.release_nonnull(); - } + if (auto style_value = interpolate_color_in_rust( + *resolved_first_color, + *resolved_second_color, + delta, + normalized_percentages.alpha_multiplier, + color_interpolation_method, + color_resolution_context)) + return style_value.release_nonnull(); // Fall back to returning a color-mix() with absolutized values if we can't compute completely. // Currently, this is only the case if one of our colors relies on `currentcolor`, as that does not compute to a color value. diff --git a/Libraries/LibWeb/CSS/StyleValues/ColorMixStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ColorMixStyleValue.h index 0139b5cf755c7..d6efa0f98b853 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ColorMixStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ColorMixStyleValue.h @@ -29,29 +29,26 @@ class ColorMixStyleValue final : public ColorStyleValue { void serialize(StringBuilder&, SerializationMode) const; private: + friend class StyleValue; + ColorMixStyleValue(RefPtr color_interpolation_method, ColorMixComponent first_component, ColorMixComponent second_component); + explicit ColorMixStyleValue(StyleValueFFI::StyleValueData const*); - static StyleValueFFI::StyleValueData* make_color_mix_data(RefPtr const& color_interpolation_method, ColorMixComponent const& first_component, ColorMixComponent const& second_component) + static StyleValueFFI::StyleValueData const* make_color_mix_data(RefPtr const& color_interpolation_method, ColorMixComponent const& first_component, ColorMixComponent const& second_component) { - // The Rust allocation takes ownership of one strong reference to each non-null value. + auto retain = [](StyleValue const* value) { + return value ? StyleValueFFI::rust_style_value_retain(value->rust_style_value_data()) : nullptr; + }; return StyleValueFFI::rust_style_value_create_color_mix( false, 0, to_underlying(ColorSyntax::Modern), - retain_style_value_for_rust(color_interpolation_method.ptr()), - retain_style_value_for_rust(first_component.color.ptr()), retain_style_value_for_rust(first_component.percentage.ptr()), - retain_style_value_for_rust(second_component.color.ptr()), retain_style_value_for_rust(second_component.percentage.ptr())); + retain(color_interpolation_method.ptr()), + retain(first_component.color.ptr()), retain(first_component.percentage.ptr()), + retain(second_component.color.ptr()), retain(second_component.percentage.ptr())); } - ValueComparingRefPtr color_interpolation_method_value() const { return static_cast(m_value->color_mix.color_interpolation_method.pointer); } - ColorMixComponent first_component() const - { - return { *static_cast(m_value->color_mix.first_color.pointer), - static_cast(m_value->color_mix.first_percentage.pointer) }; - } - ColorMixComponent second_component() const - { - return { *static_cast(m_value->color_mix.second_color.pointer), - static_cast(m_value->color_mix.second_percentage.pointer) }; - } + ValueComparingRefPtr color_interpolation_method_value() const { return m_color_interpolation_method; } + ColorMixComponent first_component() const { return m_first_component; } + ColorMixComponent second_component() const { return m_second_component; } struct NormalizedPercentages { Percentage first_percentage; @@ -66,6 +63,10 @@ class ColorMixStyleValue final : public ColorStyleValue { double alpha_multiplier; }; PercentageNormalizationResult normalize_percentages(ComputationContext const&) const; + + ValueComparingRefPtr m_color_interpolation_method; + ColorMixComponent m_first_component; + ColorMixComponent m_second_component; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h index c6ebb46cae7ad..b9bd5cc91b712 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h @@ -38,12 +38,19 @@ class ColorSchemeStyleValue final : public StyleValueWithDefaultOperators schemes, bool only) : StyleValueWithDefaultOperators(Type::ColorScheme, make_color_scheme_data(schemes, only)) { } - static StyleValueFFI::StyleValueData* make_color_scheme_data(Vector const& schemes, bool only) + static StyleValueFFI::StyleValueData const* make_color_scheme_data(Vector const& schemes, bool only) { // The Rust allocation takes ownership of one leaked reference to each scheme name. Vector raws; diff --git a/Libraries/LibWeb/CSS/StyleValues/ColorStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ColorStyleValue.h index c98a40249b90c..03f719bb0be62 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ColorStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ColorStyleValue.h @@ -68,7 +68,7 @@ class ColorStyleValue : public StyleValue { static Optional extract_channels_in_color_space(StyleValue const& origin_color, ColorType target_color_type, ColorResolutionContext const&); protected: - explicit ColorStyleValue(StyleValueFFI::StyleValueData* value) + explicit ColorStyleValue(StyleValueFFI::StyleValueData const* value) : StyleValue(Type::Color, value) { } diff --git a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.cpp index da801305bf61f..6f6b2b8d43680 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.cpp @@ -15,14 +15,36 @@ namespace Web::CSS { -StyleValueFFI::StyleValueData* ConicGradientStyleValue::make_conic_gradient_data(RefPtr const& from_angle, NonnullRefPtr const& position, Vector const& color_stop_list, GradientRepeating repeating, RefPtr const& color_interpolation_method, ColorSyntax color_syntax) +StyleValueFFI::StyleValueData const* ConicGradientStyleValue::make_conic_gradient_data(RefPtr const& from_angle, NonnullRefPtr const& position, Vector const& color_stop_list, GradientRepeating repeating, RefPtr const& color_interpolation_method, ColorSyntax color_syntax) { // The Rust allocation takes ownership of one strong reference to each non-null value. auto stops = retain_color_stops_for_rust(color_stop_list); return StyleValueFFI::rust_style_value_create_conic_gradient( - retain_style_value_for_rust(from_angle.ptr()), retain_style_value_for_rust(position.ptr()), + from_angle ? StyleValueFFI::rust_style_value_retain(from_angle->rust_style_value_data()) : nullptr, + StyleValueFFI::rust_style_value_retain(position->rust_style_value_data()), stops.data(), stops.size(), repeating == GradientRepeating::Yes, - retain_style_value_for_rust(color_interpolation_method.ptr()), to_underlying(color_syntax)); + color_interpolation_method ? StyleValueFFI::rust_style_value_retain(color_interpolation_method->rust_style_value_data()) : nullptr, + to_underlying(color_syntax)); +} + +ConicGradientStyleValue::ConicGradientStyleValue(StyleValueFFI::StyleValueData const* data) + : AbstractImageStyleValue(Type::ConicGradient, data) + , m_from_angle([&]() -> ValueComparingRefPtr { + auto const* angle_data = static_cast(data->conic_gradient.from_angle.pointer); + if (!angle_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(angle_data)); + }()) + , m_position(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(data->conic_gradient.position.pointer))) + ->as_position()) + , m_color_interpolation_method([&]() -> ValueComparingRefPtr { + auto const* method_data = static_cast(data->conic_gradient.color_interpolation_method.pointer); + if (!method_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(method_data)); + }()) +{ } void ConicGradientStyleValue::serialize(StringBuilder& builder, SerializationMode mode) const diff --git a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h index f16ba6a8dd285..3354a9a01d710 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ConicGradientStyleValue.h @@ -61,18 +61,29 @@ class WEB_API ConicGradientStyleValue final : public AbstractImageStyleValue { bool is_repeating() const { return m_value->conic_gradient.repeating; } private: + friend class StyleValue; + ConicGradientStyleValue(ValueComparingRefPtr from_angle, ValueComparingNonnullRefPtr position, Vector color_stop_list, GradientRepeating repeating, ValueComparingRefPtr color_interpolation_method, ColorSyntax color_syntax) : AbstractImageStyleValue(Type::ConicGradient, make_conic_gradient_data(from_angle, position, color_stop_list, repeating, color_interpolation_method, color_syntax)) + , m_from_angle(move(from_angle)) + , m_position(move(position)) + , m_color_interpolation_method(move(color_interpolation_method)) { } - static StyleValueFFI::StyleValueData* make_conic_gradient_data(RefPtr const&, NonnullRefPtr const&, Vector const&, GradientRepeating, RefPtr const&, ColorSyntax); + explicit ConicGradientStyleValue(StyleValueFFI::StyleValueData const*); + + static StyleValueFFI::StyleValueData const* make_conic_gradient_data(RefPtr const&, NonnullRefPtr const&, Vector const&, GradientRepeating, RefPtr const&, ColorSyntax); - ValueComparingRefPtr from_angle_value() const { return static_cast(m_value->conic_gradient.from_angle.pointer); } + ValueComparingRefPtr from_angle_value() const { return m_from_angle; } ColorSyntax gradient_color_syntax() const { return static_cast(m_value->conic_gradient.color_syntax); } - ValueComparingNonnullRefPtr position_value() const { return *static_cast(m_value->conic_gradient.position.pointer); } + ValueComparingNonnullRefPtr position_value() const { return m_position; } + + ValueComparingRefPtr color_interpolation_method_value() const { return m_color_interpolation_method; } - ValueComparingRefPtr color_interpolation_method_value() const { return static_cast(m_value->conic_gradient.color_interpolation_method.pointer); } + ValueComparingRefPtr m_from_angle; + ValueComparingNonnullRefPtr m_position; + ValueComparingRefPtr m_color_interpolation_method; mutable Optional m_resolved_size; diff --git a/Libraries/LibWeb/CSS/StyleValues/ContentStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ContentStyleValue.cpp index 37bff089268db..ffd59f6ee8e14 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ContentStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ContentStyleValue.cpp @@ -12,12 +12,11 @@ namespace Web::CSS { -StyleValueFFI::StyleValueData* ContentStyleValue::make_content_data(ValueComparingNonnullRefPtr content, ValueComparingRefPtr const& alt_text) +StyleValueFFI::StyleValueData const* ContentStyleValue::make_content_data(ValueComparingNonnullRefPtr const& content, ValueComparingRefPtr const& alt_text) { - // The Rust allocation takes ownership of one strong reference to each non-null list. - if (alt_text) - alt_text->ref(); - return StyleValueFFI::rust_style_value_create_content(&content.leak_ref(), alt_text.ptr()); + // The Rust allocation takes ownership of one strong reference to each non-null list data. + auto const* alt_text_data = alt_text ? StyleValueFFI::rust_style_value_retain(alt_text->rust_style_value_data()) : nullptr; + return StyleValueFFI::rust_style_value_create_content(StyleValueFFI::rust_style_value_retain(content->rust_style_value_data()), alt_text_data); } bool ContentStyleValue::properties_equal(ContentStyleValue const& other) const diff --git a/Libraries/LibWeb/CSS/StyleValues/ContentStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ContentStyleValue.h index b4a948e9f3c4e..7a9d0865e0cdc 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ContentStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ContentStyleValue.h @@ -21,8 +21,8 @@ class ContentStyleValue final : public StyleValueWithDefaultOperators(m_value->content.content.pointer); } - StyleValueList const* alt_text() const { return static_cast(m_value->content.alt_text.pointer); } + StyleValueList const& content() const { return m_content; } + StyleValueList const* alt_text() const { return m_alt_text.ptr(); } void serialize(StringBuilder&, SerializationMode) const; @@ -31,12 +31,28 @@ class ContentStyleValue final : public StyleValueWithDefaultOperators); private: + friend class StyleValue; + ContentStyleValue(ValueComparingNonnullRefPtr content, ValueComparingRefPtr alt_text) - : StyleValueWithDefaultOperators(Type::Content, make_content_data(move(content), alt_text)) + : StyleValueWithDefaultOperators(Type::Content, make_content_data(content, alt_text)) + , m_content(move(content)) + , m_alt_text(move(alt_text)) { } - static StyleValueFFI::StyleValueData* make_content_data(ValueComparingNonnullRefPtr, ValueComparingRefPtr const&); + explicit ContentStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::Content, data) + , m_content(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->content.content.pointer)))->as_value_list()) + { + auto const* alt_text_data = static_cast(data->content.alt_text.pointer); + if (alt_text_data) + m_alt_text = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(alt_text_data))->as_value_list(); + } + + static StyleValueFFI::StyleValueData const* make_content_data(ValueComparingNonnullRefPtr const&, ValueComparingRefPtr const&); + + ValueComparingNonnullRefPtr m_content; + ValueComparingRefPtr m_alt_text; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/ContrastColorStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/ContrastColorStyleValue.h index b885487f89d21..5e5eb168149d9 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ContrastColorStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/ContrastColorStyleValue.h @@ -26,12 +26,23 @@ class ContrastColorStyleValue final : public ColorStyleValue { void serialize(StringBuilder&, SerializationMode) const; private: + friend class StyleValue; + explicit ContrastColorStyleValue(ValueComparingNonnullRefPtr color) - : ColorStyleValue(StyleValueFFI::rust_style_value_create_contrast_color(false, 0, to_underlying(ColorSyntax::Modern), &color.leak_ref())) + : ColorStyleValue(StyleValueFFI::rust_style_value_create_contrast_color(false, 0, to_underlying(ColorSyntax::Modern), StyleValueFFI::rust_style_value_retain(color->rust_style_value_data()))) + , m_color(move(color)) { } - ValueComparingNonnullRefPtr color() const { return *static_cast(m_value->contrast_color.color.pointer); } + explicit ContrastColorStyleValue(StyleValueFFI::StyleValueData const* data) + : ColorStyleValue(data) + , m_color(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->contrast_color.color.pointer)))) + { + } + + ValueComparingNonnullRefPtr color() const { return m_color; } + + ValueComparingNonnullRefPtr m_color; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.cpp index a5eeeb849ea96..6ef1dcef202df 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.cpp @@ -9,6 +9,24 @@ namespace Web::CSS { +CounterDefinitionsStyleValue::CounterDefinitionsStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::CounterDefinitions, data) +{ + auto const& list = data->counter_definitions.counter_definitions; + m_counter_definitions.ensure_capacity(list.length); + for (size_t i = 0; i < list.length; ++i) { + auto const& definition = list.pointer[i]; + ValueComparingRefPtr value; + if (auto const* value_data = static_cast(definition.value.pointer)) + value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(value_data)); + m_counter_definitions.unchecked_append(CounterDefinition { + .name = Utf16FlyString::from_raw(definition.name.raw), + .is_reversed = definition.is_reversed, + .value = move(value), + }); + } +} + void CounterDefinitionsStyleValue::serialize(StringBuilder& builder, SerializationMode mode) const { bool first = true; diff --git a/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h index 110ba05ffd225..557a2d0e28b90 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h @@ -32,18 +32,7 @@ class WEB_API CounterDefinitionsStyleValue : public StyleValueWithDefaultOperato Vector counter_definitions() const { - auto const& list = m_value->counter_definitions.counter_definitions; - Vector definitions; - definitions.ensure_capacity(list.length); - for (size_t i = 0; i < list.length; ++i) { - auto const& definition = list.pointer[i]; - definitions.unchecked_append(CounterDefinition { - .name = Utf16FlyString::from_raw(definition.name.raw), - .is_reversed = definition.is_reversed, - .value = static_cast(definition.value.pointer), - }); - } - return definitions; + return m_counter_definitions; } void serialize(StringBuilder&, SerializationMode) const; ValueComparingNonnullRefPtr absolutized(ComputationContext const&) const; @@ -51,24 +40,28 @@ class WEB_API CounterDefinitionsStyleValue : public StyleValueWithDefaultOperato bool properties_equal(CounterDefinitionsStyleValue const& other) const; private: + friend class StyleValue; + explicit CounterDefinitionsStyleValue(Vector counter_definitions) : StyleValueWithDefaultOperators(Type::CounterDefinitions, make_counter_definitions_data(counter_definitions)) + , m_counter_definitions(move(counter_definitions)) { } - static StyleValueFFI::StyleValueData* make_counter_definitions_data(Vector const& counter_definitions) + explicit CounterDefinitionsStyleValue(StyleValueFFI::StyleValueData const*); + + static StyleValueFFI::StyleValueData const* make_counter_definitions_data(Vector const& counter_definitions) { - // The Rust allocation takes ownership of one leaked reference to each name and one - // strong reference to each non-null value. Vector ffi_definitions; ffi_definitions.ensure_capacity(counter_definitions.size()); for (auto const& definition : counter_definitions) { - if (definition.value) - definition.value->ref(); - ffi_definitions.unchecked_append({ { definition.name.to_raw_leaked() }, definition.is_reversed, { definition.value.ptr() } }); + auto const* value_data = definition.value ? StyleValueFFI::rust_style_value_retain(definition.value->rust_style_value_data()) : nullptr; + ffi_definitions.unchecked_append({ { definition.name.to_raw_leaked() }, definition.is_reversed, { value_data } }); } return StyleValueFFI::rust_style_value_create_counter_definitions(ffi_definitions.data(), ffi_definitions.size()); } + + Vector m_counter_definitions; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/CounterStyleStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/CounterStyleStyleValue.h index 3b881dd90bbca..2c7ea80370b1b 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CounterStyleStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CounterStyleStyleValue.h @@ -46,12 +46,19 @@ class CounterStyleStyleValue : public StyleValueWithDefaultOperators value) : StyleValueWithDefaultOperators(Type::CounterStyle, make_counter_style_data(value)) { } - static StyleValueFFI::StyleValueData* make_counter_style_data(Variant const& value) + static StyleValueFFI::StyleValueData const* make_counter_style_data(Variant const& value) { // The Rust allocation takes ownership of one leaked reference to each retained string. return value.visit( diff --git a/Libraries/LibWeb/CSS/StyleValues/CounterStyleSystemStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/CounterStyleSystemStyleValue.h index 9258f4590d90e..e243606a1e63e 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CounterStyleSystemStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CounterStyleSystemStyleValue.h @@ -54,7 +54,7 @@ class CounterStyleSystemStyleValue : public StyleValueWithDefaultOperators(data.system); case 1: - return Fixed { static_cast(data.first_symbol.pointer) }; + return Fixed { m_first_symbol }; default: return Extends { Utf16FlyString::from_raw(data.name.raw) }; } @@ -64,12 +64,24 @@ class CounterStyleSystemStyleValue : public StyleValueWithDefaultOperators value) : StyleValueWithDefaultOperators(Type::CounterStyleSystem, make_counter_style_system_data(value)) { + if (value.has()) + m_first_symbol = value.get().first_symbol; } - static StyleValueFFI::StyleValueData* make_counter_style_system_data(Value const& value) + explicit CounterStyleSystemStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::CounterStyleSystem, data) + { + auto const* first_symbol_data = static_cast(data->counter_style_system.first_symbol.pointer); + if (first_symbol_data) + m_first_symbol = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(first_symbol_data)); + } + + static StyleValueFFI::StyleValueData const* make_counter_style_system_data(Value const& value) { // The Rust allocation takes ownership of one strong reference to the first symbol and // one leaked reference to the name when they are present. @@ -78,14 +90,15 @@ class CounterStyleSystemStyleValue : public StyleValueWithDefaultOperatorsref(); - return StyleValueFFI::rust_style_value_create_counter_style_system(1, 0, fixed.first_symbol.ptr(), 0); + auto const* first_symbol_data = fixed.first_symbol ? StyleValueFFI::rust_style_value_retain(fixed.first_symbol->rust_style_value_data()) : nullptr; + return StyleValueFFI::rust_style_value_create_counter_style_system(1, 0, first_symbol_data, 0); }, [](Extends const& extends) { return StyleValueFFI::rust_style_value_create_counter_style_system(2, 0, nullptr, extends.name.to_raw_leaked()); }); } + + ValueComparingRefPtr m_first_symbol; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp index 08eb1e7cb14cc..929b38b5cc57e 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.cpp @@ -21,15 +21,15 @@ namespace Web::CSS { -static StyleValueFFI::StyleValueData* make_counter_data(CounterStyleValue::CounterFunction function, Utf16FlyString const& counter_name, ValueComparingNonnullRefPtr const& counter_style, Utf16FlyString const& join_string) +static StyleValueFFI::StyleValueData const* make_counter_data(CounterStyleValue::CounterFunction function, Utf16FlyString const& counter_name, ValueComparingNonnullRefPtr const& counter_style, Utf16FlyString const& join_string) { - // The Rust allocation takes ownership of one strong reference to the counter style. - counter_style->ref(); - return StyleValueFFI::rust_style_value_create_counter(to_underlying(function), counter_name.to_raw_leaked(), counter_style.ptr(), join_string.to_raw_leaked()); + // The Rust allocation takes ownership of one strong reference to the counter style data. + return StyleValueFFI::rust_style_value_create_counter(to_underlying(function), counter_name.to_raw_leaked(), StyleValueFFI::rust_style_value_retain(counter_style->rust_style_value_data()), join_string.to_raw_leaked()); } CounterStyleValue::CounterStyleValue(CounterFunction function, Utf16FlyString counter_name, ValueComparingNonnullRefPtr counter_style, Utf16FlyString join_string) : StyleValueWithDefaultOperators(Type::Counter, make_counter_data(function, counter_name, counter_style, join_string)) + , m_counter_style(move(counter_style)) { } diff --git a/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.h index 1d23ae24f7d20..9e577b27c7130 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CounterStyleValue.h @@ -31,7 +31,7 @@ class CounterStyleValue : public StyleValueWithDefaultOperators(m_value->counter.function); } Utf16FlyString counter_name() const { return Utf16FlyString::from_raw(m_value->counter.counter_name.raw); } - ValueComparingNonnullRefPtr counter_style() const { return *static_cast(m_value->counter.counter_style.pointer); } + ValueComparingNonnullRefPtr counter_style() const { return m_counter_style; } Utf16FlyString join_string() const { return Utf16FlyString::from_raw(m_value->counter.join_string.raw); } Utf16String resolve(DOM::AbstractElement&) const; @@ -41,7 +41,17 @@ class CounterStyleValue : public StyleValueWithDefaultOperators(data->counter.counter_style.pointer)))) + { + } + explicit CounterStyleValue(CounterFunction, Utf16FlyString counter_name, ValueComparingNonnullRefPtr counter_style, Utf16FlyString join_string); + + ValueComparingNonnullRefPtr m_counter_style; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.cpp index 005838fe2f9a6..45d0ea3906822 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.cpp @@ -21,16 +21,34 @@ namespace Web::CSS { -StyleValueFFI::StyleValueData* CursorStyleValue::make_cursor_data(NonnullRefPtr const& image, RefPtr const& x, RefPtr const& y) +StyleValueFFI::StyleValueData const* CursorStyleValue::make_cursor_data(NonnullRefPtr const& image, RefPtr const& x, RefPtr const& y) { // The Rust allocation takes ownership of one strong reference to the image and to each // non-null coordinate. - image->ref(); - if (x) - x->ref(); - if (y) - y->ref(); - return StyleValueFFI::rust_style_value_create_cursor(image.ptr(), x.ptr(), y.ptr()); + return StyleValueFFI::rust_style_value_create_cursor( + StyleValueFFI::rust_style_value_retain(image->rust_style_value_data()), + x ? StyleValueFFI::rust_style_value_retain(x->rust_style_value_data()) : nullptr, + y ? StyleValueFFI::rust_style_value_retain(y->rust_style_value_data()) : nullptr); +} + +CursorStyleValue::CursorStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::Cursor, data) + , m_image(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(data->cursor.image.pointer))) + ->as_abstract_image()) + , m_x([&]() -> ValueComparingRefPtr { + auto const* x_data = static_cast(data->cursor.x.pointer); + if (!x_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(x_data)); + }()) + , m_y([&]() -> ValueComparingRefPtr { + auto const* y_data = static_cast(data->cursor.y.pointer); + if (!y_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(y_data)); + }()) +{ } void CursorStyleValue::serialize(StringBuilder& builder, SerializationMode mode) const diff --git a/Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.h index eecfcfd54516b..c109611802f9e 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CursorStyleValue.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include namespace Web::CSS { @@ -25,7 +25,7 @@ class CursorStyleValue final : public StyleValueWithDefaultOperators(m_value->cursor.image.pointer); } + AbstractImageStyleValue const& image() const { return m_image; } Optional make_image_cursor(Layout::NodeWithStyle const&) const; @@ -36,19 +36,30 @@ class CursorStyleValue final : public StyleValueWithDefaultOperators image, RefPtr x, RefPtr y) : StyleValueWithDefaultOperators(Type::Cursor, make_cursor_data(image, x, y)) + , m_image(move(image)) + , m_x(move(x)) + , m_y(move(y)) { } - static StyleValueFFI::StyleValueData* make_cursor_data(NonnullRefPtr const&, RefPtr const&, RefPtr const&); + explicit CursorStyleValue(StyleValueFFI::StyleValueData const*); + + static StyleValueFFI::StyleValueData const* make_cursor_data(NonnullRefPtr const&, RefPtr const&, RefPtr const&); + + StyleValue const& image_as_style_value() const { return *m_image; } - StyleValue const& image_as_style_value() const { return *static_cast(m_value->cursor.image.pointer); } + ValueComparingRefPtr x() const { return m_x; } + ValueComparingRefPtr y() const { return m_y; } - ValueComparingRefPtr x() const { return static_cast(m_value->cursor.x.pointer); } - ValueComparingRefPtr y() const { return static_cast(m_value->cursor.y.pointer); } + ValueComparingNonnullRefPtr m_image; + ValueComparingRefPtr m_x; + ValueComparingRefPtr m_y; mutable Optional m_cached_bitmap_color; mutable Optional m_cached_bitmap; diff --git a/Libraries/LibWeb/CSS/StyleValues/CustomIdentStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/CustomIdentStyleValue.h index d70096ea64cfc..1cf555b457de4 100644 --- a/Libraries/LibWeb/CSS/StyleValues/CustomIdentStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/CustomIdentStyleValue.h @@ -30,6 +30,13 @@ class CustomIdentStyleValue final : public StyleValueWithDefaultOperators reify(JS::Realm&, Utf16FlyString const& associated_property) const; protected: - DimensionStyleValue(Type type, StyleValueFFI::StyleValueData* value) + DimensionStyleValue(Type type, StyleValueFFI::StyleValueData const* value) : StyleValue(type, value) { } diff --git a/Libraries/LibWeb/CSS/StyleValues/DisplayStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/DisplayStyleValue.h index 9ecc954249438..7fa7abb9af89f 100644 --- a/Libraries/LibWeb/CSS/StyleValues/DisplayStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/DisplayStyleValue.h @@ -25,6 +25,13 @@ class WEB_API DisplayStyleValue : public StyleValueWithDefaultOperators reify(JS::Realm&, Utf16FlyString const& associated_property) const; private: + friend class StyleValue; + + explicit DisplayStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::Display, data) + { + } + explicit DisplayStyleValue(Display const& display) : StyleValueWithDefaultOperators(Type::Display, StyleValueFFI::rust_style_value_create_display(bit_cast(display))) { diff --git a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp index af29ef2c1d662..cf812b959dc02 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.cpp @@ -17,25 +17,62 @@ namespace Web::CSS { -StyleValueFFI::StyleValueData* EasingStyleValue::make_easing_data(Function const& function) +StyleValueFFI::StyleValueData const* EasingStyleValue::make_easing_data(Function const& function) { - // The Rust allocation takes ownership of one strong reference to each non-null value. + auto retain = [](StyleValue const* value) { + return value ? StyleValueFFI::rust_style_value_retain(value->rust_style_value_data()) : nullptr; + }; return function.visit( [&](Linear const& linear) { Vector stops; stops.ensure_capacity(linear.stops.size()); for (auto const& stop : linear.stops) - stops.unchecked_append({ { retain_style_value_for_rust(stop.output.ptr()) }, { retain_style_value_for_rust(stop.input.ptr()) } }); + stops.unchecked_append({ { retain(stop.output.ptr()) }, { retain(stop.input.ptr()) } }); return StyleValueFFI::rust_style_value_create_easing(0, stops.data(), stops.size(), nullptr, nullptr, nullptr, nullptr, nullptr, 0); }, [&](CubicBezier const& bezier) { - return StyleValueFFI::rust_style_value_create_easing(1, nullptr, 0, retain_style_value_for_rust(bezier.x1.ptr()), retain_style_value_for_rust(bezier.y1.ptr()), retain_style_value_for_rust(bezier.x2.ptr()), retain_style_value_for_rust(bezier.y2.ptr()), nullptr, 0); + return StyleValueFFI::rust_style_value_create_easing(1, nullptr, 0, retain(bezier.x1.ptr()), retain(bezier.y1.ptr()), retain(bezier.x2.ptr()), retain(bezier.y2.ptr()), nullptr, 0); }, [&](Steps const& steps) { - return StyleValueFFI::rust_style_value_create_easing(2, nullptr, 0, nullptr, nullptr, nullptr, nullptr, retain_style_value_for_rust(steps.number_of_intervals.ptr()), to_underlying(steps.position)); + return StyleValueFFI::rust_style_value_create_easing(2, nullptr, 0, nullptr, nullptr, nullptr, nullptr, retain(steps.number_of_intervals.ptr()), to_underlying(steps.position)); }); } +EasingStyleValue::EasingStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::Easing, data) + , m_function([&]() -> Function { + auto adopt = [](auto const& retained) -> ValueComparingNonnullRefPtr { + auto const* child_data = static_cast(retained.pointer); + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(child_data)); + }; + auto adopt_optional = [](auto const& retained) -> ValueComparingRefPtr { + auto const* child_data = static_cast(retained.pointer); + if (!child_data) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(child_data)); + }; + auto const& easing = data->easing; + switch (easing.kind) { + case 0: { + Vector stops; + stops.ensure_capacity(easing.linear_stops.length); + for (size_t i = 0; i < easing.linear_stops.length; ++i) { + auto const& stop = easing.linear_stops.pointer[i]; + stops.unchecked_append({ adopt(stop.output), adopt_optional(stop.input) }); + } + return Linear { move(stops) }; + } + case 1: + return CubicBezier { adopt(easing.x1), adopt(easing.y1), adopt(easing.x2), adopt(easing.y2) }; + case 2: + return Steps { adopt(easing.number_of_intervals), static_cast(easing.step_position) }; + default: + VERIFY_NOT_REACHED(); + } + }()) +{ +} + EasingStyleValue::Function const& EasingStyleValue::function() const { return m_function; diff --git a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h index 05580063b2ced..e380b0d755a04 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/EasingStyleValue.h @@ -116,13 +116,17 @@ class EasingStyleValue final : public StyleValueWithDefaultOperators edge, RefPtr const& offset) - : StyleValueWithDefaultOperators(Type::Edge, make_edge_data(edge, offset)) + friend class StyleValue; + + explicit EdgeStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::Edge, data) { + auto const* offset_data = static_cast(data->edge.offset.pointer); + if (offset_data) + m_offset = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(offset_data)); } - static StyleValueFFI::StyleValueData* make_edge_data(Optional edge, RefPtr const& offset) + EdgeStyleValue(Optional edge, RefPtr const& offset) + : StyleValueWithDefaultOperators(Type::Edge, StyleValueFFI::rust_style_value_create_edge(edge.has_value(), edge.has_value() ? to_underlying(*edge) : 0, offset ? StyleValueFFI::rust_style_value_retain(offset->rust_style_value_data()) : nullptr)) + , m_offset(offset) { - // The Rust allocation takes ownership of one strong reference to the offset. - if (offset) - offset->ref(); - return StyleValueFFI::rust_style_value_create_edge(edge.has_value(), edge.has_value() ? to_underlying(*edge) : 0, offset.ptr()); } Optional edge() const @@ -51,7 +54,9 @@ class EdgeStyleValue final : public StyleValueWithDefaultOperators(m_value->edge.edge); } - ValueComparingRefPtr offset_style_value() const { return static_cast(m_value->edge.offset.pointer); } + ValueComparingRefPtr offset_style_value() const { return m_offset; } + + ValueComparingRefPtr m_offset; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/EmptyOptionalStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/EmptyOptionalStyleValue.h index 12a9f7bbdb176..af87935b616e8 100644 --- a/Libraries/LibWeb/CSS/StyleValues/EmptyOptionalStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/EmptyOptionalStyleValue.h @@ -28,6 +28,13 @@ class EmptyOptionalStyleValue final : public StyleValueWithDefaultOperators FilterStyleValue::initial_value_for(FilterStyleValue const& value, bool use_transparent_drop_shadow_color) -{ - switch (value.kind()) { - case FilterStyleValue::Kind::Blur: - return BlurFilterStyleValue::create(LengthStyleValue::create(Length::make_px(0))); - case FilterStyleValue::Kind::DropShadow: - return DropShadowFilterStyleValue::create( - LengthStyleValue::create(Length::make_px(0)), - LengthStyleValue::create(Length::make_px(0)), - LengthStyleValue::create(Length::make_px(0)), - use_transparent_drop_shadow_color ? static_cast>(ColorStyleValue::create_from_color(Color::Transparent, ColorSyntax::Legacy)) : nullptr); - case FilterStyleValue::Kind::HueRotate: - return HueRotateFilterStyleValue::create(AngleStyleValue::create(Angle::make_degrees(0))); - case FilterStyleValue::Kind::Color: { - auto const& color = static_cast(value); - auto default_value = [&]() { - switch (color.operation()) { - case Gfx::ColorFilterType::Grayscale: - case Gfx::ColorFilterType::Invert: - case Gfx::ColorFilterType::Sepia: - return 0.0; - case Gfx::ColorFilterType::Brightness: - case Gfx::ColorFilterType::Contrast: - case Gfx::ColorFilterType::Opacity: - case Gfx::ColorFilterType::Saturate: - return 1.0; - } - VERIFY_NOT_REACHED(); - }(); - return ColorFilterStyleValue::create(color.operation(), NumberStyleValue::create(default_value)); - } - } - VERIFY_NOT_REACHED(); -} - // The C++ Type is Filter for every filter kind, so filter operations dispatch on the kind. ValueComparingNonnullRefPtr FilterStyleValue::absolutized(ComputationContext const& context) const { diff --git a/Libraries/LibWeb/CSS/StyleValues/FilterStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/FilterStyleValue.h index b41e5a6743937..1c1251a6b1f5c 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FilterStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/FilterStyleValue.h @@ -30,20 +30,29 @@ class FilterStyleValue : public StyleValue { void serialize(StringBuilder&, SerializationMode) const; bool equals(StyleValue const& other) const; ValueComparingNonnullRefPtr absolutized(ComputationContext const&) const; - virtual bool contains_url() const { return false; } - static ValueComparingNonnullRefPtr initial_value_for(FilterStyleValue const&, bool use_transparent_drop_shadow_color); protected: - explicit FilterStyleValue(StyleValueFFI::StyleValueData* data) + explicit FilterStyleValue(StyleValueFFI::StyleValueData const* data) : StyleValue(Type::Filter, data) + , m_filter_value(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(static_cast(data->filter.value.pointer)))) { } - static StyleValueFFI::StyleValueData* make_filter_data(Kind kind, u8 color_operation, StyleValue const* value) + FilterStyleValue(StyleValueFFI::StyleValueData const* data, ValueComparingNonnullRefPtr value) + : StyleValue(Type::Filter, data) + , m_filter_value(move(value)) + { + } + + ValueComparingNonnullRefPtr filter_value() const { return m_filter_value; } + + static StyleValueFFI::StyleValueData const* make_filter_data(Kind kind, u8 color_operation, StyleValue const* value) { - // The Rust allocation takes ownership of one strong reference to the value. - return StyleValueFFI::rust_style_value_create_filter(to_underlying(kind), color_operation, retain_style_value_for_rust(value)); + return StyleValueFFI::rust_style_value_create_filter(to_underlying(kind), color_operation, StyleValueFFI::rust_style_value_retain(value->rust_style_value_data())); } + +private: + ValueComparingNonnullRefPtr m_filter_value; }; // https://drafts.csswg.org/filter-effects-1/#funcdef-filter-blur @@ -54,7 +63,7 @@ class BlurFilterStyleValue final : public FilterStyleValue { return adopt_ref(*new (nothrow) BlurFilterStyleValue(move(radius))); } - ValueComparingNonnullRefPtr radius() const { return *static_cast(m_value->filter.value.pointer); } + ValueComparingNonnullRefPtr radius() const { return filter_value(); } float resolved_radius() const; void serialize(StringBuilder&, SerializationMode) const; @@ -62,8 +71,15 @@ class BlurFilterStyleValue final : public FilterStyleValue { bool equals(StyleValue const&) const; private: + friend class StyleValue; + explicit BlurFilterStyleValue(ValueComparingNonnullRefPtr radius) - : FilterStyleValue(make_filter_data(Kind::Blur, 0, radius.ptr())) + : FilterStyleValue(make_filter_data(Kind::Blur, 0, radius.ptr()), radius) + { + } + + explicit BlurFilterStyleValue(StyleValueFFI::StyleValueData const* data) + : FilterStyleValue(data) { } }; @@ -93,7 +109,7 @@ class DropShadowFilterStyleValue final : public FilterStyleValue { } ValueComparingNonnullRefPtr shadow_style_value() const { return shadow(); } - ShadowStyleValue const& shadow() const { return *static_cast(m_value->filter.value.pointer); } + ShadowStyleValue const& shadow() const { return filter_value()->as_shadow(); } ValueComparingNonnullRefPtr offset_x() const { return shadow().offset_x(); } ValueComparingNonnullRefPtr offset_y() const { return shadow().offset_y(); } ValueComparingRefPtr radius() const { return shadow().blur_radius_or_null(); } @@ -104,8 +120,15 @@ class DropShadowFilterStyleValue final : public FilterStyleValue { bool equals(StyleValue const&) const; private: + friend class StyleValue; + explicit DropShadowFilterStyleValue(ValueComparingNonnullRefPtr shadow) - : FilterStyleValue(make_filter_data(Kind::DropShadow, 0, shadow.ptr())) + : FilterStyleValue(make_filter_data(Kind::DropShadow, 0, shadow.ptr()), shadow) + { + } + + explicit DropShadowFilterStyleValue(StyleValueFFI::StyleValueData const* data) + : FilterStyleValue(data) { } }; @@ -118,7 +141,7 @@ class HueRotateFilterStyleValue final : public FilterStyleValue { return adopt_ref(*new (nothrow) HueRotateFilterStyleValue(move(angle))); } - ValueComparingNonnullRefPtr angle() const { return *static_cast(m_value->filter.value.pointer); } + ValueComparingNonnullRefPtr angle() const { return filter_value(); } float angle_degrees() const; void serialize(StringBuilder&, SerializationMode) const; @@ -126,8 +149,15 @@ class HueRotateFilterStyleValue final : public FilterStyleValue { bool equals(StyleValue const&) const; private: + friend class StyleValue; + explicit HueRotateFilterStyleValue(ValueComparingNonnullRefPtr angle) - : FilterStyleValue(make_filter_data(Kind::HueRotate, 0, angle.ptr())) + : FilterStyleValue(make_filter_data(Kind::HueRotate, 0, angle.ptr()), angle) + { + } + + explicit HueRotateFilterStyleValue(StyleValueFFI::StyleValueData const* data) + : FilterStyleValue(data) { } }; @@ -142,7 +172,7 @@ class ColorFilterStyleValue final : public FilterStyleValue { } Gfx::ColorFilterType operation() const { return static_cast(m_value->filter.color_operation); } - ValueComparingNonnullRefPtr amount() const { return *static_cast(m_value->filter.value.pointer); } + ValueComparingNonnullRefPtr amount() const { return filter_value(); } float resolved_amount() const; void serialize(StringBuilder&, SerializationMode) const; @@ -150,8 +180,15 @@ class ColorFilterStyleValue final : public FilterStyleValue { bool equals(StyleValue const&) const; private: + friend class StyleValue; + ColorFilterStyleValue(Gfx::ColorFilterType operation, ValueComparingNonnullRefPtr amount) - : FilterStyleValue(make_filter_data(Kind::Color, static_cast(to_underlying(operation)), amount.ptr())) + : FilterStyleValue(make_filter_data(Kind::Color, static_cast(to_underlying(operation)), amount.ptr()), amount) + { + } + + explicit ColorFilterStyleValue(StyleValueFFI::StyleValueData const* data) + : FilterStyleValue(data) { } }; diff --git a/Libraries/LibWeb/CSS/StyleValues/FlexStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/FlexStyleValue.h index 0b5ba39003a0d..c46fe8ee4f11f 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FlexStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/FlexStyleValue.h @@ -34,6 +34,13 @@ class FlexStyleValue final : public DimensionStyleValue { } private: + friend class StyleValue; + + explicit FlexStyleValue(StyleValueFFI::StyleValueData const* data) + : DimensionStyleValue(Type::Flex, data) + { + } + FlexStyleValue(Flex&& flex) : DimensionStyleValue(Type::Flex, StyleValueFFI::rust_style_value_create_flex(flex.raw_value(), to_underlying(flex.unit()))) { diff --git a/Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.cpp index aa702c2311a18..5090f93eed61a 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.cpp @@ -11,28 +11,31 @@ namespace Web::CSS { -StyleValueFFI::StyleValueData* FontSourceStyleValue::make_font_source_data(Source const& source, Optional const& format, Vector const& tech) +StyleValueFFI::StyleValueData const* FontSourceStyleValue::make_font_source_data(Source const& source, Optional const& format, Vector const& tech) { // The Rust allocation takes ownership of one strong reference to the local name, or one // leaked reference to each retained string. bool is_local = source.has(); - void const* local_name = nullptr; + StyleValueFFI::StyleValueData const* local_name = nullptr; + String retained_url_string; FlatPtr url_string = 0; + ReadonlyBytes url_bytes; u8 url_type = 0; Vector modifiers; if (is_local) { auto const& local = source.get(); - local.name->ref(); - local_name = local.name.ptr(); + local_name = StyleValueFFI::rust_style_value_retain(local.name->rust_style_value_data()); } else { auto const& url = source.get(); - url_string = url.url().to_raw_leaked(); + retained_url_string = url.url(); + url_bytes = retained_url_string.bytes(); + url_string = retained_url_string.to_raw_leaked(); url_type = to_underlying(url.type()); modifiers = retain_url_modifiers_for_rust(url); } static_assert(sizeof(FontTech) == sizeof(u8)); return StyleValueFFI::rust_style_value_create_font_source( - is_local, local_name, url_string, url_type, modifiers.data(), modifiers.size(), + is_local, local_name, url_string, url_bytes.data(), url_bytes.size(), url_type, modifiers.data(), modifiers.size(), format.has_value(), format.has_value() ? format->to_raw_leaked() : 0, reinterpret_cast(tech.data()), tech.size()); } @@ -41,7 +44,7 @@ FontSourceStyleValue::Source FontSourceStyleValue::source() const { auto const& data = m_value->font_source; if (data.is_local) - return Local { *static_cast(data.local_name.pointer) }; + return Local { *m_local_name }; return url_from_rust_data(data.url, data.url_type, data.url_modifiers); } @@ -49,6 +52,16 @@ FontSourceStyleValue::Source FontSourceStyleValue::source() const FontSourceStyleValue::FontSourceStyleValue(Source source, Optional format, Vector tech) : StyleValueWithDefaultOperators(Type::FontSource, make_font_source_data(source, format, tech)) { + if (source.has()) + m_local_name = source.get().name; +} + +FontSourceStyleValue::FontSourceStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::FontSource, data) +{ + auto const* local_name_data = static_cast(data->font_source.local_name.pointer); + if (local_name_data) + m_local_name = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(local_name_data)); } FontSourceStyleValue::~FontSourceStyleValue() = default; diff --git a/Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.h index 6b7dcb5789f45..3abbcb80d489c 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/FontSourceStyleValue.h @@ -47,9 +47,14 @@ class FontSourceStyleValue final : public StyleValueWithDefaultOperators format, Vector tech); + explicit FontSourceStyleValue(StyleValueFFI::StyleValueData const*); + + static StyleValueFFI::StyleValueData const* make_font_source_data(Source const&, Optional const&, Vector const&); - static StyleValueFFI::StyleValueData* make_font_source_data(Source const&, Optional const&, Vector const&); + ValueComparingRefPtr m_local_name; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.cpp index c54f197fa311f..0482f835a2fec 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.cpp @@ -15,6 +15,7 @@ namespace Web::CSS { FontStyleStyleValue::FontStyleStyleValue(FontStyleKeyword font_style, ValueComparingRefPtr angle_value) : StyleValueWithDefaultOperators(Type::FontStyle, make_font_style_data(font_style, angle_value)) + , m_angle_value(move(angle_value)) { } diff --git a/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.h index e8c05f53bf37e..2aa4d55f169b9 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/FontStyleStyleValue.h @@ -20,7 +20,7 @@ class FontStyleStyleValue final : public StyleValueWithDefaultOperators(m_value->font_style.font_style); } - ValueComparingRefPtr angle() const { return static_cast(m_value->font_style.angle_value.pointer); } + ValueComparingRefPtr angle() const { return m_angle_value; } int to_font_slope() const; @@ -38,15 +38,24 @@ class FontStyleStyleValue final : public StyleValueWithDefaultOperators(data->font_style.angle_value.pointer); + if (angle_data) + m_angle_value = StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(angle_data)); + } + FontStyleStyleValue(FontStyleKeyword, ValueComparingRefPtr angle_value); - static StyleValueFFI::StyleValueData* make_font_style_data(FontStyleKeyword font_style, ValueComparingRefPtr const& angle_value) + static StyleValueFFI::StyleValueData const* make_font_style_data(FontStyleKeyword font_style, ValueComparingRefPtr const& angle_value) { - // The Rust allocation takes ownership of one strong reference to the angle value. - if (angle_value) - angle_value->ref(); - return StyleValueFFI::rust_style_value_create_font_style(to_underlying(font_style), angle_value.ptr()); + return StyleValueFFI::rust_style_value_create_font_style(to_underlying(font_style), angle_value ? StyleValueFFI::rust_style_value_retain(angle_value->rust_style_value_data()) : nullptr); } + + ValueComparingRefPtr m_angle_value; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/FrequencyStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/FrequencyStyleValue.h index cea3a4ca10c84..b2e37cb560cd1 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FrequencyStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/FrequencyStyleValue.h @@ -33,6 +33,13 @@ class FrequencyStyleValue final : public DimensionStyleValue { bool equals(StyleValue const& other) const; private: + friend class StyleValue; + + explicit FrequencyStyleValue(StyleValueFFI::StyleValueData const* data) + : DimensionStyleValue(Type::Frequency, data) + { + } + explicit FrequencyStyleValue(Frequency frequency) : DimensionStyleValue(Type::Frequency, StyleValueFFI::rust_style_value_create_frequency(frequency.raw_value(), to_underlying(frequency.unit()))) { diff --git a/Libraries/LibWeb/CSS/StyleValues/FunctionStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/FunctionStyleValue.h index ff7b9fc5bac29..b007940f539fd 100644 --- a/Libraries/LibWeb/CSS/StyleValues/FunctionStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/FunctionStyleValue.h @@ -19,7 +19,7 @@ class FunctionStyleValue : public StyleValueWithDefaultOperatorsfunction.name.raw); } - ValueComparingNonnullRefPtr value() const { return *static_cast(m_value->function.value.pointer); } + ValueComparingNonnullRefPtr value() const { return m_argument_value; } ValueComparingNonnullRefPtr absolutized(ComputationContext const&) const; void serialize(StringBuilder&, SerializationMode) const; @@ -27,12 +27,23 @@ class FunctionStyleValue : public StyleValueWithDefaultOperators(data->function.value.pointer)))) + { + } + FunctionStyleValue(Utf16FlyString name, NonnullRefPtr value) - : StyleValueWithDefaultOperators(Type::Function, StyleValueFFI::rust_style_value_create_function(name.to_raw_leaked(), &value.leak_ref())) + : StyleValueWithDefaultOperators(Type::Function, StyleValueFFI::rust_style_value_create_function(name.to_raw_leaked(), StyleValueFFI::rust_style_value_retain(value->rust_style_value_data()))) + , m_argument_value(move(value)) { } virtual ~FunctionStyleValue() override = default; + + ValueComparingNonnullRefPtr m_argument_value; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h index 1ae89928e5410..17a3a032f4a21 100644 --- a/Libraries/LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/GridAutoFlowStyleValue.h @@ -31,6 +31,13 @@ class GridAutoFlowStyleValue final : public StyleValueWithDefaultOperators grid_areas, size_t row_count, size_t column_count) : StyleValueWithDefaultOperators(Type::GridTemplateArea, make_grid_template_area_data(grid_areas, row_count, column_count)) { } - static StyleValueFFI::StyleValueData* make_grid_template_area_data(HashMap const& grid_areas, size_t row_count, size_t column_count) + static StyleValueFFI::StyleValueData const* make_grid_template_area_data(HashMap const& grid_areas, size_t row_count, size_t column_count) { // The Rust allocation takes ownership of one leaked reference to each area name. Vector areas; diff --git a/Libraries/LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h index 4db919757896a..811aa5c0c7187 100644 --- a/Libraries/LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/GridTrackPlacementStyleValue.h @@ -21,18 +21,7 @@ class GridTrackPlacementStyleValue final : public StyleValueWithDefaultOperators GridTrackPlacement grid_track_placement() const { - auto const& data = m_value->grid_track_placement; - Optional name; - if (data.has_name) - name = Utf16FlyString::from_raw(data.name.raw); - switch (data.kind) { - case 0: - return GridTrackPlacement::make_auto(); - case 1: - return GridTrackPlacement::make_span(*static_cast(data.value.pointer), move(name)); - default: - return GridTrackPlacement::make_line(static_cast(data.value.pointer), move(name)); - } + return m_grid_track_placement; } void serialize(StringBuilder&, SerializationMode) const; @@ -41,36 +30,63 @@ class GridTrackPlacementStyleValue final : public StyleValueWithDefaultOperators bool properties_equal(GridTrackPlacementStyleValue const& other) const { return grid_track_placement() == other.grid_track_placement(); } private: + friend class StyleValue; + + explicit GridTrackPlacementStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::GridTrackPlacement, data) + , m_grid_track_placement(placement_from_data(data)) + { + } + explicit GridTrackPlacementStyleValue(GridTrackPlacement grid_track_placement) : StyleValueWithDefaultOperators(Type::GridTrackPlacement, make_grid_track_placement_data(grid_track_placement)) + , m_grid_track_placement(move(grid_track_placement)) { } - static StyleValueFFI::StyleValueData* make_grid_track_placement_data(GridTrackPlacement const& placement) + static GridTrackPlacement placement_from_data(StyleValueFFI::StyleValueData const* data) { - // The Rust allocation takes ownership of one strong reference to the value and one - // leaked reference to the name when they are present. + auto const& placement = data->grid_track_placement; + Optional name; + if (placement.has_name) + name = Utf16FlyString::from_raw(placement.name.raw); + auto* value_data = static_cast(placement.value.pointer); + switch (placement.kind) { + case 0: + return GridTrackPlacement::make_auto(); + case 1: + VERIFY(value_data); + return GridTrackPlacement::make_span(StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(value_data)), move(name)); + default: + return GridTrackPlacement::make_line(value_data ? RefPtr { StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain(value_data)) } : nullptr, move(name)); + } + } + + static StyleValueFFI::StyleValueData const* make_grid_track_placement_data(GridTrackPlacement const& placement) + { + // The Rust allocation takes ownership of one strong reference to the value data and + // one leaked reference to the name when they are present. u8 kind = 0; - void const* value = nullptr; + StyleValueFFI::StyleValueData const* value = nullptr; Optional name; if (placement.is_span()) { kind = 1; auto span_value = placement.span(); - span_value->ref(); - value = span_value.ptr(); + value = StyleValueFFI::rust_style_value_retain(span_value->rust_style_value_data()); name = placement.span_name(); } else if (placement.is_area_or_line()) { kind = 2; if (placement.has_line_number()) { auto line_number = placement.line_number(); - line_number->ref(); - value = line_number.ptr(); + value = StyleValueFFI::rust_style_value_retain(line_number->rust_style_value_data()); } if (placement.has_identifier()) name = placement.identifier(); } return StyleValueFFI::rust_style_value_create_grid_track_placement(kind, value, name.has_value(), name.has_value() ? name->to_raw_leaked() : 0); } + + GridTrackPlacement m_grid_track_placement; }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.cpp index 824d571fdb782..eefae89aa2e7e 100644 --- a/Libraries/LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.cpp @@ -25,7 +25,7 @@ static Vector const& build_grid_track_entry_ StyleValueFFI::GridTrackEntryInput input {}; entry.visit( [&](GridLineNames const& line_names) { - input.kind = 0; + input.kind = StyleValueFFI::GridTrackEntryKind::LineNames; Vector raws; raws.ensure_capacity(line_names.names().size()); for (auto const& name : line_names.names()) @@ -36,17 +36,17 @@ static Vector const& build_grid_track_entry_ }, [&](ExplicitGridTrack const& track) { if (track.is_default()) { - input.kind = 1; - input.size_value = retain_style_value_for_rust(track.grid_size().style_value().ptr()); + input.kind = StyleValueFFI::GridTrackEntryKind::Size; + input.size_value = StyleValueFFI::rust_style_value_retain(track.grid_size().style_value()->rust_style_value_data()); } else if (track.is_minmax()) { - input.kind = 2; - input.min_value = retain_style_value_for_rust(track.minmax().min_grid_size().style_value().ptr()); - input.max_value = retain_style_value_for_rust(track.minmax().max_grid_size().style_value().ptr()); + input.kind = StyleValueFFI::GridTrackEntryKind::MinMax; + input.min_value = StyleValueFFI::rust_style_value_retain(track.minmax().min_grid_size().style_value()->rust_style_value_data()); + input.max_value = StyleValueFFI::rust_style_value_retain(track.minmax().max_grid_size().style_value()->rust_style_value_data()); } else { auto const& repeat = track.repeat(); - input.kind = 3; + input.kind = StyleValueFFI::GridTrackEntryKind::Repeat; input.repeat_type = static_cast(to_underlying(repeat.type())); - input.repeat_count = retain_style_value_for_rust(repeat.repeat_count_style_value().ptr()); + input.repeat_count = repeat.repeat_count_style_value() ? StyleValueFFI::rust_style_value_retain(repeat.repeat_count_style_value()->rust_style_value_data()) : nullptr; input.repeat_is_subgrid = repeat.grid_track_size_list().is_subgrid(); input.repeat_preserve_line_name_sets = repeat.grid_track_size_list().preserves_line_name_sets(); auto const& nested = build_grid_track_entry_inputs(repeat.grid_track_size_list(), arena); @@ -60,7 +60,7 @@ static Vector const& build_grid_track_entry_ return arena.entry_arrays.last(); } -StyleValueFFI::StyleValueData* GridTrackSizeListStyleValue::make_grid_track_size_list_data(CSS::GridTrackSizeList const& list) +StyleValueFFI::StyleValueData const* GridTrackSizeListStyleValue::make_grid_track_size_list_data(CSS::GridTrackSizeList const& list) { // The Rust allocation takes ownership of one strong reference to each value and one leaked // reference to each line name. @@ -71,28 +71,34 @@ StyleValueFFI::StyleValueData* GridTrackSizeListStyleValue::make_grid_track_size static GridTrackSizeList materialize_grid_track_size_list(bool is_subgrid, bool preserve_line_name_sets, StyleValueFFI::RetainedGridTrackEntry const* entries, size_t entry_count) { + auto materialize_style_value = [](void const* pointer) -> ValueComparingRefPtr { + if (!pointer) + return nullptr; + return StyleValue::adopt_rust_style_value_data(StyleValueFFI::rust_style_value_retain( + static_cast(pointer))); + }; auto list = is_subgrid ? GridTrackSizeList::make_subgrid() : preserve_line_name_sets ? GridTrackSizeList::make_line_name_list() : GridTrackSizeList::make_none(); for (size_t i = 0; i < entry_count; ++i) { auto const& entry = entries[i]; switch (entry.kind) { - case 0: { + case StyleValueFFI::GridTrackEntryKind::LineNames: { GridLineNames names; for (size_t j = 0; j < entry.names.length; ++j) names.append(Utf16FlyString::from_raw(entry.names.pointer[j].raw)); list.append(move(names)); break; } - case 1: - list.append(ExplicitGridTrack { GridSize { *static_cast(entry.size_value.pointer) } }); + case StyleValueFFI::GridTrackEntryKind::Size: + list.append(ExplicitGridTrack { GridSize { *materialize_style_value(entry.size_value.pointer) } }); break; - case 2: - list.append(ExplicitGridTrack { GridMinMax { GridSize { *static_cast(entry.min_value.pointer) }, GridSize { *static_cast(entry.max_value.pointer) } } }); + case StyleValueFFI::GridTrackEntryKind::MinMax: + list.append(ExplicitGridTrack { GridMinMax { GridSize { *materialize_style_value(entry.min_value.pointer) }, GridSize { *materialize_style_value(entry.max_value.pointer) } } }); break; - default: { + case StyleValueFFI::GridTrackEntryKind::Repeat: { auto nested = materialize_grid_track_size_list(entry.repeat_is_subgrid, entry.repeat_preserve_line_name_sets, entry.repeat_entries_pointer, entry.repeat_entries_length); - list.append(ExplicitGridTrack { GridRepeat { static_cast(entry.repeat_type), move(nested), static_cast(entry.repeat_count.pointer) } }); + list.append(ExplicitGridTrack { GridRepeat { static_cast(entry.repeat_type), move(nested), materialize_style_value(entry.repeat_count.pointer) } }); break; } } diff --git a/Libraries/LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h index 027b86c9304dd..9b7a3a2221a15 100644 --- a/Libraries/LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/GridTrackSizeListStyleValue.h @@ -33,15 +33,20 @@ class GridTrackSizeListStyleValue final : public StyleValueWithDefaultOperators< bool properties_equal(GridTrackSizeListStyleValue const& other) const { return grid_track_size_list() == other.grid_track_size_list(); } - bool is_computationally_independent() const { return grid_track_size_list().is_computationally_independent(); } - private: + friend class StyleValue; + explicit GridTrackSizeListStyleValue(CSS::GridTrackSizeList grid_track_size_list) : StyleValueWithDefaultOperators(Type::GridTrackSizeList, make_grid_track_size_list_data(grid_track_size_list)) { } - static StyleValueFFI::StyleValueData* make_grid_track_size_list_data(CSS::GridTrackSizeList const&); + explicit GridTrackSizeListStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::GridTrackSizeList, data) + { + } + + static StyleValueFFI::StyleValueData const* make_grid_track_size_list_data(CSS::GridTrackSizeList const&); }; } diff --git a/Libraries/LibWeb/CSS/StyleValues/GuaranteedInvalidStyleValue.h b/Libraries/LibWeb/CSS/StyleValues/GuaranteedInvalidStyleValue.h index c1cb794d6a6a0..12a00fff212e5 100644 --- a/Libraries/LibWeb/CSS/StyleValues/GuaranteedInvalidStyleValue.h +++ b/Libraries/LibWeb/CSS/StyleValues/GuaranteedInvalidStyleValue.h @@ -28,6 +28,13 @@ class GuaranteedInvalidStyleValue final : public StyleValueWithDefaultOperators< bool properties_equal(GuaranteedInvalidStyleValue const&) const { return true; } private: + friend class StyleValue; + + explicit GuaranteedInvalidStyleValue(StyleValueFFI::StyleValueData const* data) + : StyleValueWithDefaultOperators(Type::GuaranteedInvalid, data) + { + } + GuaranteedInvalidStyleValue() : StyleValueWithDefaultOperators(Type::GuaranteedInvalid, StyleValueFFI::rust_style_value_create_guaranteed_invalid()) { diff --git a/Libraries/LibWeb/CSS/StyleValues/ImageSetStyleValue.cpp b/Libraries/LibWeb/CSS/StyleValues/ImageSetStyleValue.cpp index ec3f8b7f0571d..373555760f6e7 100644 --- a/Libraries/LibWeb/CSS/StyleValues/ImageSetStyleValue.cpp +++ b/Libraries/LibWeb/CSS/StyleValues/ImageSetStyleValue.cpp @@ -19,18 +19,16 @@ namespace Web::CSS { -StyleValueFFI::StyleValueData* ImageSetStyleValue::make_image_set_data(Vector