diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index 012a4267b1..abf912ee9f 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -1114,8 +1114,11 @@ static ErrorOr> interpolate_property(StyleValue const& ErrorOr StyleComputer::Animation::collect_into(StyleProperties& style_properties, RuleCache const& rule_cache) const { - if (remaining_delay.to_milliseconds() != 0) - return {}; + if (remaining_delay.to_milliseconds() != 0) { + // If the fill mode is backwards or both, we'll pretend that the animation is started, but stuck at progress 0 + if (fill_mode != CSS::AnimationFillMode::Backwards && fill_mode != CSS::AnimationFillMode::Both) + return {}; + } auto matching_keyframes = rule_cache.rules_by_animation_keyframes.get(name); if (!matching_keyframes.has_value()) @@ -1163,6 +1166,11 @@ ErrorOr StyleComputer::Animation::collect_into(StyleProperties& style_prop dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Animation {} contains {} properties to interpolate, progress = {}%", name, valid_properties, progress_in_keyframe * 100); + if (fill_mode == CSS::AnimationFillMode::Forwards || fill_mode == CSS::AnimationFillMode::Both) { + if (!active_state_if_fill_forward) + active_state_if_fill_forward = make(); + } + UnderlyingType property_id_value = 0; for (auto const& property : keyframe_values.resolved_properties) { auto property_id = static_cast(property_id_value++); @@ -1173,11 +1181,11 @@ ErrorOr StyleComputer::Animation::collect_into(StyleProperties& style_prop return property.visit( [](Empty) -> RefPtr { VERIFY_NOT_REACHED(); }, [&](AnimationKeyFrameSet::ResolvedKeyFrame::UseInitial) { - if (auto value = initial_state[to_underlying(property_id)]) + if (auto value = initial_state.state[to_underlying(property_id)]) return value; auto value = style_properties.maybe_null_property(property_id); - initial_state[to_underlying(property_id)] = value; + initial_state.state[to_underlying(property_id)] = value; return value; }, [&](RefPtr value) { return value; }); @@ -1206,6 +1214,8 @@ ErrorOr StyleComputer::Animation::collect_into(StyleProperties& style_prop auto next_value = TRY(interpolate_property(*start, *end, progress_in_keyframe)); dbgln_if(LIBWEB_CSS_ANIMATION_DEBUG, "Interpolated value for property {} at {}: {} -> {} = {}", string_from_property_id(property_id), progress_in_keyframe, start->to_string(), end->to_string(), next_value->to_string()); style_properties.set_property(property_id, next_value); + if (active_state_if_fill_forward) + active_state_if_fill_forward->state[to_underlying(property_id)] = next_value; } return {}; @@ -1248,29 +1258,29 @@ void StyleComputer::ensure_animation_timer() const break; case AnimationStepTransition::IdleOrBeforeToAfter: // FIXME: Dispatch `animationstart` then `animationend`. - m_finished_animations.set(it.key); + m_finished_animations.set(it.key, move(it.value->active_state_if_fill_forward)); break; case AnimationStepTransition::ActiveToBefore: // FIXME: Dispatch `animationend`. - m_finished_animations.set(it.key); + m_finished_animations.set(it.key, move(it.value->active_state_if_fill_forward)); break; case AnimationStepTransition::ActiveToActiveChangingTheIteration: // FIXME: Dispatch `animationiteration`. break; case AnimationStepTransition::ActiveToAfter: // FIXME: Dispatch `animationend`. - m_finished_animations.set(it.key); + m_finished_animations.set(it.key, move(it.value->active_state_if_fill_forward)); break; case AnimationStepTransition::AfterToActive: // FIXME: Dispatch `animationstart`. break; case AnimationStepTransition::AfterToBefore: // FIXME: Dispatch `animationstart` then `animationend`. - m_finished_animations.set(it.key); + m_finished_animations.set(it.key, move(it.value->active_state_if_fill_forward)); break; case AnimationStepTransition::Cancelled: // FIXME: Dispatch `animationcancel`. - m_finished_animations.set(it.key); + m_finished_animations.set(it.key, nullptr); break; } if (it.value->is_done()) @@ -1346,9 +1356,17 @@ ErrorOr StyleComputer::compute_cascaded_values(StyleProperties& style, DOM .element = &element, }; - if (m_finished_animations.contains(animation_key)) { + if (auto finished_state = m_finished_animations.get(animation_key); finished_state.has_value()) { // We've already finished going through this animation, so drop it from the active animations. m_active_animations.remove(animation_key); + // If the animation's fill mode was set to forwards/both, we need to collect and use the final frame's styles. + if (*finished_state) { + auto& state = (*finished_state)->state; + for (size_t property_id_value = 0; property_id_value < state.size(); ++property_id_value) { + if (auto& property_value = state[property_id_value]) + style.set_property(static_cast(property_id_value), *property_value); + } + } } else if (auto name = TRY(animation_name->to_string()); !name.is_empty()) { auto active_animation = m_active_animations.get(animation_key); if (!active_animation.has_value()) { @@ -1369,13 +1387,19 @@ ErrorOr StyleComputer::compute_cascaded_values(StyleProperties& style, DOM iteration_count = static_cast(iteration_count_value->as_numeric().number()); } + CSS::AnimationFillMode fill_mode { CSS::AnimationFillMode::None }; + if (auto fill_mode_property = style.maybe_null_property(PropertyID::AnimationFillMode); fill_mode_property && fill_mode_property->is_identifier()) { + if (auto fill_mode_value = value_id_to_animation_fill_mode(fill_mode_property->to_identifier()); fill_mode_value.has_value()) + fill_mode = *fill_mode_value; + } + auto animation = make(Animation { .name = move(name), .duration = duration, .delay = delay, .iteration_count = iteration_count, .direction = Animation::Direction::Normal, - .fill_mode = Animation::FillMode::None, + .fill_mode = fill_mode, .owning_element = TRY(element.try_make_weak_ptr()), .progress = CSS::Percentage(0), .remaining_delay = delay, diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.h b/Userland/Libraries/LibWeb/CSS/StyleComputer.h index 4410818aa5..024c6eee85 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.h +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.h @@ -185,6 +185,10 @@ private: Active, }; + struct AnimationStateSnapshot { + Array, to_underlying(last_property_id) + 1> state; + }; + struct Animation { String name; CSS::Time duration; @@ -197,7 +201,9 @@ private: CSS::Percentage progress { 0 }; CSS::Time remaining_delay { 0, CSS::Time::Type::Ms }; AnimationState current_state { AnimationState::Before }; - mutable Array, to_underlying(last_property_id) + 1> initial_state {}; + + mutable AnimationStateSnapshot initial_state {}; + mutable OwnPtr active_state_if_fill_forward {}; AnimationStepTransition step(CSS::Time const& time_step); ErrorOr collect_into(StyleProperties&, RuleCache const&) const; @@ -205,7 +211,7 @@ private: }; mutable HashMap> m_active_animations; - mutable HashTable m_finished_animations; + mutable HashMap> m_finished_animations; // If fill-mode is forward/both, this is non-null and contains the final state. mutable RefPtr m_animation_driver_timer; };