diff --git a/core/animation/animation.cc b/core/animation/animation.cc index f1dd522d8a..84dc90a8c8 100644 --- a/core/animation/animation.cc +++ b/core/animation/animation.cc @@ -10,6 +10,7 @@ #include +#include #include #include @@ -27,6 +28,10 @@ namespace { constexpr int64_t kThirtyMinutesInSeconds = 1800; +// Allocates nonzero Animation ids that are not reused during the process +// lifetime, including across page and Element lifecycles. +std::atomic g_next_animation_id{1}; + } // namespace namespace lynx { @@ -44,7 +49,24 @@ void SuppressSampleSideEffects(KeyframeEffect::KeyframeSampleResult& result) { } // namespace Animation::Animation(const base::String& name) - : name_(name), keyframe_effect_(nullptr) {} + : name_(name), + id_(g_next_animation_id.fetch_add(1, std::memory_order_relaxed)), + keyframe_effect_(nullptr) {} + +fml::TimeDelta Animation::GetCurrentTime() const { + if (start_time_ == fml::TimePoint::Min() || + start_time_ == GetAnimationDummyStartTime() || + current_run_start_system_time_ == fml::TimePoint::Min()) { + return fml::TimeDelta::Zero(); + } + if (state_ == State::kPause) { + return current_time_at_pause_; + } + fml::TimeDelta elapsed = + current_time_at_pause_ + + (fml::TimePoint::Now() - current_run_start_system_time_); + return elapsed < fml::TimeDelta::Zero() ? fml::TimeDelta::Zero() : elapsed; +} void Animation::Play(bool play_handles_initial_frame) { if (state_ == State::kPlay) { @@ -55,12 +77,17 @@ void Animation::Play(bool play_handles_initial_frame) { State temp_state = state_; if (temp_state == State::kIdle || temp_state == State::kStop) { ResetPauseTiming(); + current_run_start_system_time_ = fml::TimePoint::Min(); ClearSampleHistory(); } else { // Resume keeps the last valid sample history, but drops same-timestamp // cache. InvalidateSampleCache(); } + if (temp_state == State::kPause && + current_run_start_system_time_ != fml::TimePoint::Min()) { + current_run_start_system_time_ = fml::TimePoint::Now(); + } // Since `DoFrame` may reads and modifies state_, the change of state_ must be // completed before DoFrame is executed. state_ = State::kPlay; @@ -101,6 +128,7 @@ void Animation::Pause() { if (state_ == State::kPause) { return; } + current_time_at_pause_ = GetCurrentTime(); InvalidateSampleCache(); state_ = State::kPause; } @@ -167,6 +195,10 @@ bool Animation::Tick(fml::TimePoint& time) { start_time_ = time; keyframe_effect_->SetStartTime(time, reset_effect_state); } + if (state_ == State::kPlay && time != GetAnimationDummyStartTime() && + current_run_start_system_time_ == fml::TimePoint::Min()) { + current_run_start_system_time_ = fml::TimePoint::Now(); + } return keyframe_effect_->TickKeyframeModel(time).has_finished_all; } @@ -271,6 +303,10 @@ KeyframeEffect::KeyframeSampleResult Animation::SampleAt( start_time_ = frame_time; keyframe_effect_->SetStartTime(frame_time, reset_effect_state); } + if (state_ == State::kPlay && frame_time != GetAnimationDummyStartTime() && + current_run_start_system_time_ == fml::TimePoint::Min()) { + current_run_start_system_time_ = fml::TimePoint::Now(); + } // Resolve the timestamp that should be sampled. Paused animations keep // sampling at pause_time_ so repeated resolves return a frozen style. @@ -371,6 +407,7 @@ void Animation::NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern type) { void Animation::ResetPauseTiming() { pause_time_ = fml::TimePoint::Min(); total_paused_duration_ = fml::TimeDelta::Zero(); + current_time_at_pause_ = fml::TimeDelta::Zero(); was_paused_ = false; } diff --git a/core/animation/animation.h b/core/animation/animation.h index 78f4fada0c..c01ffdbce4 100644 --- a/core/animation/animation.h +++ b/core/animation/animation.h @@ -9,12 +9,14 @@ #ifndef CORE_ANIMATION_ANIMATION_H_ #define CORE_ANIMATION_ANIMATION_H_ +#include #include #include #include #include "base/include/fml/time/time_point.h" #include "core/animation/keyframe_effect.h" +#include "core/base/lynx_export.h" namespace lynx { namespace base { @@ -42,6 +44,17 @@ class Animation : public std::enable_shared_from_this { static fml::TimePoint& GetAnimationDummyStartTime(); enum class State { kIdle = 0, kPlay, kPause, kStop }; + + // Origin category used by the CDP Animation domain. + enum class Origin : uint8_t { + // Animation created from an author-defined CSS @keyframes rule. + kCSSAnimation = 0, + // Animation created by the CSS transition manager. + kCSSTransition, + // Animation created through Lynx's imperative Animate or AnimateV2 API. + kWebAnimation, + }; + Animation(const base::String& name); ~Animation() = default; void Play(bool play_handles_initial_frame = true); @@ -61,6 +74,13 @@ class Animation : public std::enable_shared_from_this { void SendIterationEvent(); const base::String& name() { return name_; } + + // Stable process-unique identifier used as CDP Animation.id. + int64_t id() const { return id_; } + + // Returns the current timeline time without mutating animation state. + LYNX_EXPORT_FOR_DEVTOOL fml::TimeDelta GetCurrentTime() const; + const fml::TimePoint& start_time() const { return start_time_; } const fml::TimePoint& pause_time() const { return pause_time_; } const fml::TimeDelta& total_paused_duration() const { @@ -115,6 +135,10 @@ class Animation : public std::enable_shared_from_this { bool GetTransitionFlag() { return is_transition_; } + Origin GetOrigin() const { return origin_; } + + void SetOrigin(Origin origin) { origin_ = origin; } + void NotifyElementSizeUpdated(); void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern); @@ -137,6 +161,8 @@ class Animation : public std::enable_shared_from_this { void ClearSampleHistory(); AnimationDelegate* animation_delegate_{nullptr}; base::String name_; + // Process-unique identifier assigned once when the object is constructed. + int64_t id_{0}; std::unique_ptr keyframe_effect_; starlight::AnimationData animation_data_; @@ -149,9 +175,15 @@ class Animation : public std::enable_shared_from_this { State state_{State::kIdle}; bool is_transition_ = false; + // Creation entry point exposed through the CDP Animation domain. + Origin origin_{Origin::kCSSAnimation}; bool need_report_over_time_{true}; fml::TimePoint pause_time_{fml::TimePoint::Min()}; fml::TimeDelta total_paused_duration_{fml::TimeDelta::Zero()}; + // Inspector timeline tracking. It uses the FML monotonic clock and does not + // depend on the platform-specific vsync timestamp epoch. + fml::TimeDelta current_time_at_pause_{fml::TimeDelta::Zero()}; + fml::TimePoint current_run_start_system_time_{fml::TimePoint::Min()}; bool was_paused_{false}; bool has_cached_sample_{false}; fml::TimePoint cached_sample_time_{fml::TimePoint::Min()}; diff --git a/core/animation/animation_unittest.cc b/core/animation/animation_unittest.cc index d67c40e99e..48faacac67 100644 --- a/core/animation/animation_unittest.cc +++ b/core/animation/animation_unittest.cc @@ -2,7 +2,9 @@ // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. +#include #include +#include #include "core/animation/css_keyframe_manager.h" #include "core/animation/keyframe_effect.h" @@ -775,6 +777,115 @@ TEST_F(AnimationTest, TestDurationZero) { } } +// CDP Animation: each Animation gets a stable, process-unique numeric id that +// is never reused, so records cleared on navigation can never collide with +// fresh ids. +TEST_F(AnimationTest, IdIsUniqueAndNonZero) { + auto a1 = InitTestAnimation(); + auto a2 = InitTestAnimation(); + EXPECT_GT(a1->id(), 0); + EXPECT_GT(a2->id(), 0); + EXPECT_NE(a1->id(), a2->id()); +} + +// CDP Animation: the origin tags an animation's entry point so the Inspector +// can map it to CSSAnimation / CSSTransition / WebAnimation. It defaults to +// CSSAnimation (CSS @keyframes) and is settable by the manager. +TEST_F(AnimationTest, OriginDefaultsToCSSAnimationAndIsSettable) { + auto a = InitTestAnimation(); + EXPECT_EQ(a->GetOrigin(), animation::Animation::Origin::kCSSAnimation); + a->SetOrigin(animation::Animation::Origin::kCSSTransition); + EXPECT_EQ(a->GetOrigin(), animation::Animation::Origin::kCSSTransition); + a->SetOrigin(animation::Animation::Origin::kWebAnimation); + EXPECT_EQ(a->GetOrigin(), animation::Animation::Origin::kWebAnimation); +} + +// CDP Animation.getCurrentTime: the timeline time is zero before the animation +// has a real start time. +TEST_F(AnimationTest, GetCurrentTimeIsZeroBeforeStart) { + auto a = InitTestAnimation(); + EXPECT_EQ(a->GetCurrentTime(), fml::TimeDelta::Zero()); + + a->Pause(); + EXPECT_EQ(a->GetCurrentTime(), fml::TimeDelta::Zero()); + EXPECT_EQ(a->GetState(), animation::Animation::State::kPause); +} + +// Pausing snapshots currentTime immediately. Inspector queries must remain +// stable before the animation receives another frame, and replaying a stopped +// animation must replace the old snapshot. +TEST_F(AnimationTest, GetCurrentTimeRemainsFrozenWhilePaused) { + auto a = InitTestAnimation(); + auto data = InitAnimationData(lynx::base::String("test_animation"), 3000, 0, + starlight::TimingFunctionData(), 1, + starlight::AnimationFillModeType::kBoth, + starlight::AnimationDirectionType::kNormal, + starlight::AnimationPlayStateType::kRunning); + a->UpdateAnimationData(data); + a->Play(false); + + auto start_time = fml::TimePoint::FromEpochDelta( + fml::TimeDelta::FromSecondsF(1000000000.0)); + a->SampleAt(start_time); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + EXPECT_GT(a->GetCurrentTime(), fml::TimeDelta::Zero()); + a->Pause(); + + const auto first_pause_current_time = a->GetCurrentTime(); + EXPECT_EQ(a->GetState(), animation::Animation::State::kPause); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + EXPECT_EQ(a->GetCurrentTime(), first_pause_current_time); + EXPECT_EQ(a->GetState(), animation::Animation::State::kPause); + + a->Play(false); + const auto current_time_at_resume = a->GetCurrentTime(); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + EXPECT_GT(a->GetCurrentTime(), current_time_at_resume); + a->Pause(); + + a->Stop(); + a->Play(false); + EXPECT_EQ(a->GetCurrentTime(), fml::TimeDelta::Zero()); + auto restart_time = start_time + fml::TimeDelta::FromSecondsF(1.0); + a->SampleAt(restart_time); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + a->Pause(); + + const auto second_pause_current_time = a->GetCurrentTime(); + EXPECT_GT(second_pause_current_time, fml::TimeDelta::Zero()); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + EXPECT_EQ(a->GetCurrentTime(), second_pause_current_time); +} + +// CDP Animation.getCurrentTime is strictly read-only: querying it must not +// advance, pause, restart, or otherwise mutate the animation's state, either +// before start or after the first frame. +TEST_F(AnimationTest, GetCurrentTimeIsReadOnly) { + auto a = InitTestAnimation(); + auto data = InitAnimationData(lynx::base::String("test_animation"), 3000, 0, + starlight::TimingFunctionData(), 1, + starlight::AnimationFillModeType::kBoth, + starlight::AnimationDirectionType::kNormal, + starlight::AnimationPlayStateType::kRunning); + a->UpdateAnimationData(data); + + // Before start: the query must not change state. + auto state_before = a->GetState(); + EXPECT_EQ(a->GetCurrentTime(), fml::TimeDelta::Zero()); + EXPECT_EQ(a->GetState(), state_before); + + a->Play(false); + fml::TimePoint t0 = + fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSecondsF(1.0)); + a->DoFrame(t0); + + // After the first frame: current time is non-negative and the query must not + // mutate state. + auto state_after_start = a->GetState(); + EXPECT_GE(a->GetCurrentTime(), fml::TimeDelta::Zero()); + EXPECT_EQ(a->GetState(), state_after_start); +} + } // namespace testing } // namespace tasm } // namespace lynx diff --git a/core/animation/css_keyframe_manager.cc b/core/animation/css_keyframe_manager.cc index 2ecb8478da..2851f3a194 100644 --- a/core/animation/css_keyframe_manager.cc +++ b/core/animation/css_keyframe_manager.cc @@ -313,6 +313,7 @@ void CSSKeyframeManager::SetAnimationDataAndPlayInternal( // Update an existing animation, add it to temp_active_animations_map_ and // delete it from animations_map_; if (force_rebuild) { + const auto origin = animation->second->GetOrigin(); if (use_new_pipeline_cleanup) { PrepareAnimationRemoval(animation->second, new_base_resolved_styles, new_underlying_layout_only_styles); @@ -320,7 +321,7 @@ void CSSKeyframeManager::SetAnimationDataAndPlayInternal( animation->second->Destroy(); } auto recreated_animation = - CreateAnimation(data, new_base_custom_properties); + CreateAnimation(data, new_base_custom_properties, origin); if (recreated_animation != nullptr) { temp_active_animations_map_[data.name] = recreated_animation; } @@ -334,6 +335,7 @@ void CSSKeyframeManager::SetAnimationDataAndPlayInternal( if (animation->second->GetState() == Animation::State::kStop && HasNoSampleableKeyframes(animation->second, has_custom_property_keyframes)) { + const auto origin = animation->second->GetOrigin(); if (use_new_pipeline_cleanup) { PrepareAnimationRemoval(animation->second, new_base_resolved_styles, new_underlying_layout_only_styles); @@ -341,7 +343,7 @@ void CSSKeyframeManager::SetAnimationDataAndPlayInternal( animation->second->Destroy(); } auto recreated_animation = - CreateAnimation(data, new_base_custom_properties); + CreateAnimation(data, new_base_custom_properties, origin); if (recreated_animation != nullptr) { temp_active_animations_map_[data.name] = recreated_animation; } @@ -726,9 +728,17 @@ bool CSSKeyframeManager::NeedsFutureTickForNewPipeline() const { has_running_animation(temp_keep_animations_map_); } +Animation::Origin CSSKeyframeManager::ResolveAnimationOrigin( + const starlight::AnimationData& data) const { + return element_->HasImperativeAnimationMetadata(data.name) + ? Animation::Origin::kWebAnimation + : Animation::Origin::kCSSAnimation; +} + std::shared_ptr CSSKeyframeManager::CreateAnimation( starlight::AnimationData& data, - const tasm::CustomPropertiesMap* base_custom_properties) { + const tasm::CustomPropertiesMap* base_custom_properties, + std::optional origin) { // 1. create animation & keyframe_effect according to animation data auto animation = std::make_shared(data.name); animation->set_animation_data(data); @@ -743,6 +753,8 @@ std::shared_ptr CSSKeyframeManager::CreateAnimation( animation->SetKeyframeEffect(std::move(keyframe_effect)); animation->BindDelegate(this); animation->BindElement(this->element()); + animation->SetOrigin(origin.has_value() ? *origin + : ResolveAnimationOrigin(data)); // 2. create keyframe Models& animation Curves according to CSS keyframe // tokens MakeKeyframeModel(animation.get(), data.name, base_custom_properties); diff --git a/core/animation/css_keyframe_manager.h b/core/animation/css_keyframe_manager.h index fd2bce21aa..d9b2b4d1ae 100644 --- a/core/animation/css_keyframe_manager.h +++ b/core/animation/css_keyframe_manager.h @@ -6,6 +6,7 @@ #define CORE_ANIMATION_CSS_KEYFRAME_MANAGER_H_ #include +#include #include #include #include @@ -155,9 +156,13 @@ class CSSKeyframeManager : public AnimationDelegate { void NotifyUnitValuesUpdatedToAnimation(tasm::CSSValuePattern); protected: + virtual Animation::Origin ResolveAnimationOrigin( + const starlight::AnimationData& data) const; + std::shared_ptr CreateAnimation( starlight::AnimationData& data, - const tasm::CustomPropertiesMap* base_custom_properties = nullptr); + const tasm::CustomPropertiesMap* base_custom_properties = nullptr, + std::optional origin = std::nullopt); void SetAnimationDataAndPlayInternal( base::Vector& anim_data, bool force_rebuild, diff --git a/core/animation/css_keyframe_manager_unittest.cc b/core/animation/css_keyframe_manager_unittest.cc index ceb48edfe0..f8784cc9c0 100644 --- a/core/animation/css_keyframe_manager_unittest.cc +++ b/core/animation/css_keyframe_manager_unittest.cc @@ -528,6 +528,8 @@ TEST_F(CSSKeyframeManagerTest, HasTwoSameAnimation) { starlight::AnimationPlayStateType::kRunning)); test_manager->SetAnimationDataAndPlay(animation_data); EXPECT_TRUE(test_manager->animations_map().count(base::String("test"))); + EXPECT_EQ(animation::Animation::Origin::kCSSAnimation, + test_manager->animations_map()[base::String("test")]->GetOrigin()); EXPECT_TRUE(test_manager->animations_map()[base::String("test")] ->get_animation_data() .duration == 3000); diff --git a/core/animation/css_transition_manager.h b/core/animation/css_transition_manager.h index d45eec5355..f048a21599 100644 --- a/core/animation/css_transition_manager.h +++ b/core/animation/css_transition_manager.h @@ -92,6 +92,11 @@ class CSSTransitionManager : public CSSKeyframeManager { bool IsShouldTransitionType(starlight::AnimationPropertyType type); protected: + Animation::Origin ResolveAnimationOrigin( + const starlight::AnimationData&) const override { + return Animation::Origin::kCSSTransition; + } + base::LinearFlatMap transition_data_; base::LinearFlatMap keyframe_tokens_; base::LinearFlatSet property_types_; diff --git a/core/animation/css_transition_manager_unittest.cc b/core/animation/css_transition_manager_unittest.cc index 1ad0147056..7e3e316aed 100644 --- a/core/animation/css_transition_manager_unittest.cc +++ b/core/animation/css_transition_manager_unittest.cc @@ -496,6 +496,9 @@ TEST_F(CSSTransitionManagerTest, NoNeedUpdateExistingAnimator) { tasm::CSSValue(1, CSSValuePattern::NUMBER)); // Animation map check EXPECT_TRUE(test_manager->animations_map().count(base::String("opacity"))); + EXPECT_EQ( + animation::Animation::Origin::kCSSTransition, + test_manager->animations_map()[base::String("opacity")]->GetOrigin()); starlight::AnimationData& opacity_animation_data = test_manager->animations_map()[base::String("opacity")] ->get_animation_data(); diff --git a/core/renderer/dom/BUILD.gn b/core/renderer/dom/BUILD.gn index 0c8013f605..b7438aa751 100644 --- a/core/renderer/dom/BUILD.gn +++ b/core/renderer/dom/BUILD.gn @@ -107,6 +107,7 @@ unittest_set("dom_testset") { "fiber/text_element_unittest.cc", "fragment/fragment_unittest.cc", "fragment/text_fragment_behavior_unittest.cc", + "imperative_animation_metadata_unittest.cc", "imperative_animation_state_unittest.cc", "lynx_element_query_unittest.cc", "testing/fiber_element_test.cc", diff --git a/core/renderer/dom/build.gni b/core/renderer/dom/build.gni index 3ee7c5d772..8b60362875 100644 --- a/core/renderer/dom/build.gni +++ b/core/renderer/dom/build.gni @@ -53,6 +53,9 @@ lynx_element_shared_sources = [ "element_property.cc", "element_tree_serializer.cc", "element_tree_serializer.h", + "imperative_animation_metadata.cc", + "imperative_animation_metadata.h", + "imperative_animation_source.h", "imperative_animation_state.cc", "imperative_animation_state.h", ] diff --git a/core/renderer/dom/element.cc b/core/renderer/dom/element.cc index 05ffc03c61..f8a5b6a58a 100644 --- a/core/renderer/dom/element.cc +++ b/core/renderer/dom/element.cc @@ -1351,40 +1351,32 @@ void Element::Animate(const lepus::Value& args, } UnitHandler::Process(id, value, styles, parser_configs); } - if (track_imperative_animations) { - RecordImperativeAnimationStart( - ImperativeAnimationState::Source::kAnimate, js_name, animate_name, - owns_generated_keyframe, styles); - } + RecordImperativeAnimationStart(ImperativeAnimationSource::kAnimate, + js_name, animate_name, + owns_generated_keyframe, styles); break; } case runtime::js::JavaScriptElement::AnimationOperation::PAUSE: { BASE_STATIC_STRING_DECL(kPaused, "paused"); UnitHandler::Process(kPropertyIDAnimationPlayState, lepus::Value(kPaused), styles, parser_configs); - if (track_imperative_animations) { - UpdateImperativeAnimationPlayState( - ImperativeAnimationState::Source::kAnimate, - args.GetProperty(1).String(), styles, true); - } + UpdateImperativeAnimationPlayState(ImperativeAnimationSource::kAnimate, + args.GetProperty(1).String(), styles, + true); break; } case runtime::js::JavaScriptElement::AnimationOperation::PLAY: { BASE_STATIC_STRING_DECL(kRunning, "running"); UnitHandler::Process(kPropertyIDAnimationPlayState, lepus::Value(kRunning), styles, parser_configs); - if (track_imperative_animations) { - UpdateImperativeAnimationPlayState( - ImperativeAnimationState::Source::kAnimate, - args.GetProperty(1).String(), styles, false); - } + UpdateImperativeAnimationPlayState(ImperativeAnimationSource::kAnimate, + args.GetProperty(1).String(), styles, + false); break; } case runtime::js::JavaScriptElement::AnimationOperation::CANCEL: { - if (track_imperative_animations) { - CancelImperativeAnimation(ImperativeAnimationState::Source::kAnimate, - args.GetProperty(1).String()); - } + CancelImperativeAnimation(ImperativeAnimationSource::kAnimate, + args.GetProperty(1).String()); BASE_STATIC_STRING_DECL(kRunning, "running"); UnitHandler::Process(kPropertyIDAnimationPlayState, lepus::Value(kRunning), styles, parser_configs); @@ -1399,10 +1391,8 @@ void Element::Animate(const lepus::Value& args, break; } case runtime::js::JavaScriptElement::AnimationOperation::FINISH: { - if (track_imperative_animations) { - FinishImperativeAnimation(ImperativeAnimationState::Source::kAnimate, - args.GetProperty(1).String()); - } + FinishImperativeAnimation(ImperativeAnimationSource::kAnimate, + args.GetProperty(1).String()); break; } default: @@ -1477,11 +1467,9 @@ void Element::AnimateV2(const lepus::Value& args, } UnitHandler::Process(id, value, styles, parser_configs); } - if (track_imperative_animations) { - RecordImperativeAnimationStart( - ImperativeAnimationState::Source::kAnimateV2, js_name, animate_name, - owns_generated_keyframe, styles); - } + RecordImperativeAnimationStart(ImperativeAnimationSource::kAnimateV2, + js_name, animate_name, + owns_generated_keyframe, styles); break; } case runtime::js::JavaScriptElement::AnimationOperation::PAUSE: { @@ -1495,11 +1483,9 @@ void Element::AnimateV2(const lepus::Value& args, UnitHandler::Process(kPropertyIDAnimationName, lepus::Value(args.GetProperty(1).StdString()), styles, parser_configs); - if (track_imperative_animations) { - UpdateImperativeAnimationPlayState( - ImperativeAnimationState::Source::kAnimateV2, - args.GetProperty(1).String(), styles, true); - } + UpdateImperativeAnimationPlayState(ImperativeAnimationSource::kAnimateV2, + args.GetProperty(1).String(), styles, + true); break; } case runtime::js::JavaScriptElement::AnimationOperation::PLAY: { @@ -1513,18 +1499,14 @@ void Element::AnimateV2(const lepus::Value& args, UnitHandler::Process(kPropertyIDAnimationName, lepus::Value(args.GetProperty(1).StdString()), styles, parser_configs); - if (track_imperative_animations) { - UpdateImperativeAnimationPlayState( - ImperativeAnimationState::Source::kAnimateV2, - args.GetProperty(1).String(), styles, false); - } + UpdateImperativeAnimationPlayState(ImperativeAnimationSource::kAnimateV2, + args.GetProperty(1).String(), styles, + false); break; } case runtime::js::JavaScriptElement::AnimationOperation::CANCEL: { - if (track_imperative_animations) { - CancelImperativeAnimation(ImperativeAnimationState::Source::kAnimateV2, - args.GetProperty(1).String()); - } + CancelImperativeAnimation(ImperativeAnimationSource::kAnimateV2, + args.GetProperty(1).String()); BASE_STATIC_STRING_DECL(kRunning, "running"); UnitHandler::Process(kPropertyIDAnimationPlayState, lepus::Value(kRunning), styles, parser_configs); @@ -1539,10 +1521,8 @@ void Element::AnimateV2(const lepus::Value& args, break; } case runtime::js::JavaScriptElement::AnimationOperation::FINISH: { - if (track_imperative_animations) { - FinishImperativeAnimation(ImperativeAnimationState::Source::kAnimateV2, - args.GetProperty(1).String()); - } + FinishImperativeAnimation(ImperativeAnimationSource::kAnimateV2, + args.GetProperty(1).String()); break; } default: @@ -3201,9 +3181,8 @@ void Element::SetDefaultOverflow(bool visible) { } void Element::DestroyPlatformNode() { - if (ShouldTrackImperativeAnimationsForNewPipeline()) { - ClearImperativeAnimationState(); - } + imperative_animation_metadata_.reset(); + ClearImperativeAnimationState(); if (element_container() && has_painting_node_) { element_container()->Destroy(); } diff --git a/core/renderer/dom/element.h b/core/renderer/dom/element.h index daca5e1cce..326a812965 100644 --- a/core/renderer/dom/element.h +++ b/core/renderer/dom/element.h @@ -43,6 +43,7 @@ #include "core/renderer/dom/attribute_holder.h" #include "core/renderer/dom/base_element_container.h" #include "core/renderer/dom/element_property.h" +#include "core/renderer/dom/imperative_animation_metadata.h" #include "core/renderer/dom/imperative_animation_state.h" #include "core/renderer/dom/selector/selector_item.h" #include "core/renderer/dom/style_resolver.h" @@ -335,6 +336,10 @@ class Element : public lepus::RefCounted, SLNode* slnode() const { return sl_node_.get(); } ElementManager* element_manager() const { return element_manager_; } + + // Returns true when active imperative-animation metadata owns + // |animation_name|. Used to classify a newly created Animation's origin. + bool HasImperativeAnimationMetadata(const base::String& animation_name) const; Element* parent() const { return parent_; } Element* next_sibling() const { return Sibling(1); } Element* previous_sibling() const { return Sibling(-1); } @@ -2067,17 +2072,18 @@ class Element : public lepus::RefCounted, inline void MarkRequireFlush() { flush_required_ = true; } bool ShouldTrackImperativeAnimationsForNewPipeline() const; - void RecordImperativeAnimationStart(ImperativeAnimationState::Source source, + void RecordImperativeAnimationStart(ImperativeAnimationSource source, const base::String& js_name, const base::String& animation_name, bool owns_generated_keyframe, const StyleMap& timing_styles); - void UpdateImperativeAnimationPlayState( - ImperativeAnimationState::Source source, const base::String& name, - const StyleMap& timing_styles, bool paused); - void CancelImperativeAnimation(ImperativeAnimationState::Source source, + void UpdateImperativeAnimationPlayState(ImperativeAnimationSource source, + const base::String& name, + const StyleMap& timing_styles, + bool paused); + void CancelImperativeAnimation(ImperativeAnimationSource source, const base::String& name); - void FinishImperativeAnimation(ImperativeAnimationState::Source source, + void FinishImperativeAnimation(ImperativeAnimationSource source, const base::String& name); void ClearImperativeAnimationsForStyleAnimationUpdate(); void ReplayImperativeAnimationsToStyle( @@ -2281,6 +2287,12 @@ class Element : public lepus::RefCounted, base::auto_create_optional keyframes_map_; // Save increase key of the Animate API. base::String will_removed_keyframe_name_; + // Source identity used by both styling pipelines to classify a newly created + // Animation. It does not own runtime timing or fill state. + base::auto_create_optional + imperative_animation_metadata_; + // Runtime timing, replay, and cleanup state used only by the new styling + // pipeline. ImperativeAnimationState imperative_animation_state_; // for global-bind event base::auto_create_optional> diff --git a/core/renderer/dom/element_imperative_animation.cc b/core/renderer/dom/element_imperative_animation.cc index 70bf844ed8..1830ea1229 100644 --- a/core/renderer/dom/element_imperative_animation.cc +++ b/core/renderer/dom/element_imperative_animation.cc @@ -51,10 +51,12 @@ void Element::RemoveOwnedImperativeAnimationKeyframe( } } -void Element::RecordImperativeAnimationStart( - ImperativeAnimationState::Source source, const base::String& js_name, - const base::String& animation_name, bool owns_generated_keyframe, - const StyleMap& timing_styles) { +void Element::RecordImperativeAnimationStart(ImperativeAnimationSource source, + const base::String& js_name, + const base::String& animation_name, + bool owns_generated_keyframe, + const StyleMap& timing_styles) { + imperative_animation_metadata_->RecordStart(source, js_name, animation_name); if (!ShouldTrackImperativeAnimationsForNewPipeline()) { return; } @@ -71,7 +73,7 @@ void Element::RecordImperativeAnimationStart( } void Element::UpdateImperativeAnimationPlayState( - ImperativeAnimationState::Source source, const base::String& name, + ImperativeAnimationSource source, const base::String& name, const StyleMap& timing_styles, bool paused) { if (!ShouldTrackImperativeAnimationsForNewPipeline()) { return; @@ -80,8 +82,11 @@ void Element::UpdateImperativeAnimationPlayState( paused); } -void Element::CancelImperativeAnimation(ImperativeAnimationState::Source source, +void Element::CancelImperativeAnimation(ImperativeAnimationSource source, const base::String& name) { + if (auto* metadata = imperative_animation_metadata_.get()) { + metadata->Cancel(source, name); + } if (!ShouldTrackImperativeAnimationsForNewPipeline()) { return; } @@ -89,8 +94,11 @@ void Element::CancelImperativeAnimation(ImperativeAnimationState::Source source, imperative_animation_state_.Cancel(source, name)); } -void Element::FinishImperativeAnimation(ImperativeAnimationState::Source source, +void Element::FinishImperativeAnimation(ImperativeAnimationSource source, const base::String& name) { + if (auto* metadata = imperative_animation_metadata_.get()) { + metadata->Finish(source, name); + } if (!ShouldTrackImperativeAnimationsForNewPipeline()) { return; } @@ -99,6 +107,7 @@ void Element::FinishImperativeAnimation(ImperativeAnimationState::Source source, } void Element::ClearImperativeAnimationsForStyleAnimationUpdate() { + imperative_animation_metadata_.reset(); if (!ShouldTrackImperativeAnimationsForNewPipeline()) { return; } @@ -135,6 +144,12 @@ bool Element::HasImperativeAnimations() const { return imperative_animation_state_.HasRecords(); } +bool Element::HasImperativeAnimationMetadata( + const base::String& animation_name) const { + const auto* metadata = imperative_animation_metadata_.get(); + return metadata && metadata->HasAnimationName(animation_name); +} + void Element::ClearImperativeAnimationState() { if (!ShouldTrackImperativeAnimationsForNewPipeline()) { return; diff --git a/core/renderer/dom/element_unittest.cc b/core/renderer/dom/element_unittest.cc index 649aac575c..62b1b2550b 100644 --- a/core/renderer/dom/element_unittest.cc +++ b/core/renderer/dom/element_unittest.cc @@ -220,9 +220,15 @@ TEST_F(ElementTest, ResolveCSSKeyframesByNames) { TEST_F(ElementTest, Animate_Array) { auto element = manager->CreateFiberElement("view"); + EXPECT_FALSE(element->imperative_animation_metadata_.has_value()); + EXPECT_FALSE(element->HasImperativeAnimationMetadata(base::String("name1"))); + EXPECT_FALSE(element->imperative_animation_metadata_.has_value()); auto array1 = lepus::CArray::Create(); - array1->set(0, lepus_value(0)); + array1->set( + 0, + lepus_value(runtime::js::JavaScriptElement::AnimationOperation::START)); + array1->set(1, lepus_value("animation-handle")); auto array2 = lepus::CArray::Create(); auto table1 = lepus::Dictionary::Create(); @@ -249,6 +255,37 @@ TEST_F(ElementTest, Animate_Array) { lepus::Value test_animate_args{array1}; auto pipeline_option = std::make_shared(); element->Animate(test_animate_args, pipeline_option); + EXPECT_TRUE(element->imperative_animation_metadata_.has_value()); + EXPECT_TRUE(element->HasImperativeAnimationMetadata(base::String("name1"))); + EXPECT_FALSE(element->imperative_animation_state_.HasRecords()); + element->SetDataToNativeKeyframeAnimator(false); + ASSERT_NE(nullptr, element->css_keyframe_manager_); + auto animation_iter = + element->css_keyframe_manager_->animations_map_.find("name1"); + ASSERT_NE(animation_iter, + element->css_keyframe_manager_->animations_map_.end()); + EXPECT_EQ(animation::Animation::Origin::kWebAnimation, + animation_iter->second->GetOrigin()); + + array1->set( + 0, + lepus_value(runtime::js::JavaScriptElement::AnimationOperation::PAUSE)); + element->Animate(test_animate_args, pipeline_option); + element->SetDataToNativeKeyframeAnimator(false); + EXPECT_FALSE(element->imperative_animation_state_.HasRecords()); + EXPECT_EQ( + animation::Animation::State::kPause, + element->css_keyframe_manager_->animations_map_["name1"]->GetState()); + + array1->set( + 0, lepus_value(runtime::js::JavaScriptElement::AnimationOperation::PLAY)); + element->Animate(test_animate_args, pipeline_option); + element->SetDataToNativeKeyframeAnimator(false); + EXPECT_FALSE(element->imperative_animation_state_.HasRecords()); + EXPECT_EQ( + animation::Animation::State::kPlay, + element->css_keyframe_manager_->animations_map_["name1"]->GetState()); + auto iter = element->keyframes_map_->find("name1"); EXPECT_EQ(iter != element->keyframes_map_->end(), true); EXPECT_EQ(iter->second->GetKeyframesContent() @@ -271,6 +308,72 @@ TEST_F(ElementTest, Animate_Array) { ->second->find(kPropertyIDLeft) ->second.GetPattern(), CSSValuePattern::PX); + + auto animation_data = element->computed_css_style()->animation_data(); + auto web_animation = element->css_keyframe_manager_->animations_map_["name1"]; + array1->set( + 0, + lepus_value(runtime::js::JavaScriptElement::AnimationOperation::FINISH)); + element->Animate(test_animate_args, pipeline_option); + EXPECT_FALSE(element->HasImperativeAnimationMetadata(base::String("name1"))); + EXPECT_EQ(animation::Animation::Origin::kWebAnimation, + web_animation->GetOrigin()); + + element->css_keyframe_manager_->SyncAnimationDataForNewPipeline( + animation_data, true); + auto rebuilt_animation = + element->css_keyframe_manager_->animations_map_["name1"]; + EXPECT_NE(web_animation, rebuilt_animation); + EXPECT_EQ(animation::Animation::Origin::kWebAnimation, + rebuilt_animation->GetOrigin()); + + base::Vector no_animation_data; + element->css_keyframe_manager_->SetAnimationDataAndPlay(no_animation_data); + element->css_keyframe_manager_->SetAnimationDataAndPlay(animation_data); + EXPECT_EQ( + animation::Animation::Origin::kCSSAnimation, + element->css_keyframe_manager_->animations_map_["name1"]->GetOrigin()); +} + +TEST_F(ElementTest, AnimateV2UsesMetadataWithoutLegacyPipelineState) { + auto element = manager->CreateFiberElement("view"); + element->enable_new_animator_ = true; + + auto args = lepus::CArray::Create(); + args->set(0, lepus_value( + runtime::js::JavaScriptElement::AnimationOperation::START)); + args->set(1, lepus_value("v2-handle")); + + auto keyframes = lepus::CArray::Create(); + auto first_frame = lepus::Dictionary::Create(); + first_frame->SetValue("opacity", lepus_value(0)); + keyframes->set(0, lepus::Value(first_frame)); + auto second_frame = lepus::Dictionary::Create(); + second_frame->SetValue("opacity", lepus_value(1)); + keyframes->set(1, lepus::Value(second_frame)); + args->set(2, lepus::Value(keyframes)); + + auto options = lepus::Dictionary::Create(); + options->SetValue("name", lepus::Value("v2-animation")); + options->SetValue("duration", lepus::Value(2000)); + args->set(3, lepus::Value(options)); + + auto pipeline_option = std::make_shared(); + element->AnimateV2(lepus::Value(args), pipeline_option); + + EXPECT_TRUE(element->HasImperativeAnimationMetadata("v2-animation")); + EXPECT_FALSE(element->imperative_animation_state_.HasRecords()); + element->SetDataToNativeKeyframeAnimator(false); + ASSERT_NE(nullptr, element->css_keyframe_manager_); + ASSERT_TRUE( + element->css_keyframe_manager_->animations_map_.contains("v2-animation")); + EXPECT_EQ(animation::Animation::Origin::kWebAnimation, + element->css_keyframe_manager_->animations_map_["v2-animation"] + ->GetOrigin()); + + element->DestroyPlatformNode(); + EXPECT_FALSE(element->imperative_animation_metadata_.has_value()); + EXPECT_FALSE(element->HasImperativeAnimationMetadata("v2-animation")); } TEST_F(ElementTest, Animate_Table) { diff --git a/core/renderer/dom/imperative_animation_metadata.cc b/core/renderer/dom/imperative_animation_metadata.cc new file mode 100644 index 0000000000..cde8efd514 --- /dev/null +++ b/core/renderer/dom/imperative_animation_metadata.cc @@ -0,0 +1,61 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "core/renderer/dom/imperative_animation_metadata.h" + +namespace lynx { +namespace tasm { + +void ImperativeAnimationMetadata::RecordStart( + ImperativeAnimationSource source, const base::String& js_name, + const base::String& animation_name) { + for (auto iter = records_.begin(); iter != records_.end();) { + const bool same_identity = + iter->source == source && + ((js_name.empty() && animation_name.empty()) || + (!js_name.empty() && + (iter->js_name == js_name || iter->animation_name == js_name)) || + (!animation_name.empty() && (iter->js_name == animation_name || + iter->animation_name == animation_name))); + if (iter->source == source && + (source == ImperativeAnimationSource::kAnimate || same_identity)) { + iter = records_.erase(iter); + } else { + ++iter; + } + } + records_.emplace_back(Record{source, js_name, animation_name}); +} + +void ImperativeAnimationMetadata::Cancel(ImperativeAnimationSource source, + const base::String& name) { + for (auto iter = records_.begin(); iter != records_.end();) { + if (iter->source == source && (name.empty() || iter->js_name == name || + iter->animation_name == name)) { + iter = records_.erase(iter); + } else { + ++iter; + } + } +} + +void ImperativeAnimationMetadata::Finish(ImperativeAnimationSource source, + const base::String& name) { + Cancel(source, name); +} + +void ImperativeAnimationMetadata::Clear() { records_.clear(); } + +bool ImperativeAnimationMetadata::HasAnimationName( + const base::String& animation_name) const { + for (const auto& record : records_) { + if (record.animation_name == animation_name) { + return true; + } + } + return false; +} + +} // namespace tasm +} // namespace lynx diff --git a/core/renderer/dom/imperative_animation_metadata.h b/core/renderer/dom/imperative_animation_metadata.h new file mode 100644 index 0000000000..2481542c67 --- /dev/null +++ b/core/renderer/dom/imperative_animation_metadata.h @@ -0,0 +1,63 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef CORE_RENDERER_DOM_IMPERATIVE_ANIMATION_METADATA_H_ +#define CORE_RENDERER_DOM_IMPERATIVE_ANIMATION_METADATA_H_ + +#include "base/include/value/base_string.h" +#include "base/include/vector.h" +#include "core/renderer/dom/imperative_animation_source.h" + +namespace lynx { +namespace tasm { + +// Tracks only the identity needed to classify animations created by +// imperative animation APIs. Runtime animation state is owned elsewhere. +class ImperativeAnimationMetadata { + public: + // Records the identity of an animation before CSSKeyframeManager creates its + // Animation object. Animate replaces its previous entry, while AnimateV2 + // replaces only an entry with the same identity. + void RecordStart(ImperativeAnimationSource source, + const base::String& js_name, + const base::String& animation_name); + + // Removes entries matched by either their JS-facing name or final animation + // name. Source is also matched so the two imperative APIs remain isolated. + void Cancel(ImperativeAnimationSource source, const base::String& name); + + // Ends source tracking even when runtime fill state must remain. An Animation + // object that has already been created keeps its Origin on the object itself. + void Finish(ImperativeAnimationSource source, const base::String& name); + + // Clears all source metadata when animation data or the owning platform node + // is discarded. + void Clear(); + + // Returns whether an active imperative entry owns |animation_name|. This is + // queried only when a new Animation object needs its initial Origin. + bool HasAnimationName(const base::String& animation_name) const; + + private: + // Identity required to correlate an imperative API operation with the final + // CSS animation name consumed by CSSKeyframeManager. Runtime timing and fill + // state intentionally remain in ImperativeAnimationState. + struct Record { + // API variant that created this entry. + ImperativeAnimationSource source{ImperativeAnimationSource::kAnimate}; + // Handle supplied by the JavaScript API and used by later operations. + base::String js_name; + // Final animation-name written into style and used to create Animation. + base::String animation_name; + }; + + // Active entries that have started but have not been canceled, finished, or + // invalidated by an owning Element lifecycle change. + base::Vector records_; +}; + +} // namespace tasm +} // namespace lynx + +#endif // CORE_RENDERER_DOM_IMPERATIVE_ANIMATION_METADATA_H_ diff --git a/core/renderer/dom/imperative_animation_metadata_unittest.cc b/core/renderer/dom/imperative_animation_metadata_unittest.cc new file mode 100644 index 0000000000..a72aa05603 --- /dev/null +++ b/core/renderer/dom/imperative_animation_metadata_unittest.cc @@ -0,0 +1,59 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#include "core/renderer/dom/imperative_animation_metadata.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace lynx { +namespace tasm { +namespace testing { + +TEST(ImperativeAnimationMetadataTest, StartUsesSourceSpecificIdentity) { + ImperativeAnimationMetadata metadata; + metadata.RecordStart(ImperativeAnimationSource::kAnimate, "first-js", + "first-animation"); + metadata.RecordStart(ImperativeAnimationSource::kAnimate, "second-js", + "second-animation"); + EXPECT_FALSE(metadata.HasAnimationName("first-animation")); + EXPECT_TRUE(metadata.HasAnimationName("second-animation")); + + metadata.RecordStart(ImperativeAnimationSource::kAnimateV2, "first-js", + "first-animation"); + metadata.RecordStart(ImperativeAnimationSource::kAnimateV2, "second-js", + "second-animation"); + metadata.RecordStart(ImperativeAnimationSource::kAnimateV2, "first-js", + "first-animation-updated"); + + EXPECT_FALSE(metadata.HasAnimationName("first-animation")); + EXPECT_TRUE(metadata.HasAnimationName("first-animation-updated")); + EXPECT_TRUE(metadata.HasAnimationName("second-animation")); +} + +TEST(ImperativeAnimationMetadataTest, EndAndClearRemoveRecords) { + ImperativeAnimationMetadata metadata; + metadata.RecordStart(ImperativeAnimationSource::kAnimateV2, "cancel-js", + "cancel-animation"); + metadata.RecordStart(ImperativeAnimationSource::kAnimateV2, "finish-js", + "finish-animation"); + + metadata.Cancel(ImperativeAnimationSource::kAnimateV2, "cancel-js"); + metadata.Finish(ImperativeAnimationSource::kAnimateV2, "finish-animation"); + + EXPECT_FALSE(metadata.HasAnimationName("cancel-animation")); + EXPECT_FALSE(metadata.HasAnimationName("finish-animation")); + metadata.RecordStart(ImperativeAnimationSource::kAnimate, "animate-js", + "animate-animation"); + metadata.RecordStart(ImperativeAnimationSource::kAnimateV2, "animate-v2-js", + "animate-v2-animation"); + + metadata.Clear(); + + EXPECT_FALSE(metadata.HasAnimationName("animate-animation")); + EXPECT_FALSE(metadata.HasAnimationName("animate-v2-animation")); +} + +} // namespace testing +} // namespace tasm +} // namespace lynx diff --git a/core/renderer/dom/imperative_animation_source.h b/core/renderer/dom/imperative_animation_source.h new file mode 100644 index 0000000000..9ee0de55fd --- /dev/null +++ b/core/renderer/dom/imperative_animation_source.h @@ -0,0 +1,26 @@ +// Copyright 2026 The Lynx Authors. All rights reserved. +// Licensed under the Apache License Version 2.0 that can be found in the +// LICENSE file in the root directory of this source tree. + +#ifndef CORE_RENDERER_DOM_IMPERATIVE_ANIMATION_SOURCE_H_ +#define CORE_RENDERER_DOM_IMPERATIVE_ANIMATION_SOURCE_H_ + +#include + +namespace lynx { +namespace tasm { + +// Identifies the Lynx API entry point that owns an imperative animation. +// Both values map to Animation::Origin::kWebAnimation, but remain distinct +// here because Animate and AnimateV2 use different replacement rules. +enum class ImperativeAnimationSource : uint8_t { + // Native Element.animate(): a new start replaces the previous Animate entry. + kAnimate, + // SelectorQuery AnimateV2: entries are independently addressed by identity. + kAnimateV2, +}; + +} // namespace tasm +} // namespace lynx + +#endif // CORE_RENDERER_DOM_IMPERATIVE_ANIMATION_SOURCE_H_ diff --git a/core/renderer/dom/imperative_animation_state.cc b/core/renderer/dom/imperative_animation_state.cc index bb96b0ca33..67287f4176 100644 --- a/core/renderer/dom/imperative_animation_state.cc +++ b/core/renderer/dom/imperative_animation_state.cc @@ -72,8 +72,8 @@ bool ImperativeAnimationState::MatchesName(const Record& record, } bool ImperativeAnimationState::MatchesIdentity( - const Record& record, Source source, const base::String& js_name, - const base::String& animation_name) { + const Record& record, ImperativeAnimationSource source, + const base::String& js_name, const base::String& animation_name) { if (record.source != source) { return false; } @@ -84,19 +84,19 @@ bool ImperativeAnimationState::MatchesIdentity( } bool ImperativeAnimationState::ShouldReplaceOnStart( - const Record& record, Source source, const base::String& js_name, - const base::String& animation_name) { + const Record& record, ImperativeAnimationSource source, + const base::String& js_name, const base::String& animation_name) { if (record.source != source) { return false; } - if (source == Source::kAnimate) { + if (source == ImperativeAnimationSource::kAnimate) { return true; } return MatchesIdentity(record, source, js_name, animation_name); } ImperativeAnimationState::Mutation ImperativeAnimationState::RecordStart( - Source source, const base::String& js_name, + ImperativeAnimationSource source, const base::String& js_name, const base::String& animation_name, bool owns_generated_keyframe, const StyleMap& timing_styles, CSSKeyframesToken* keyframes_token) { Mutation mutation; @@ -131,7 +131,7 @@ ImperativeAnimationState::Mutation ImperativeAnimationState::RecordStart( return mutation; } -void ImperativeAnimationState::UpdatePlayState(Source source, +void ImperativeAnimationState::UpdatePlayState(ImperativeAnimationSource source, const base::String& name, const StyleMap& timing_styles, bool paused) { @@ -147,7 +147,7 @@ void ImperativeAnimationState::UpdatePlayState(Source source, } ImperativeAnimationState::Mutation ImperativeAnimationState::Cancel( - Source source, const base::String& name) { + ImperativeAnimationSource source, const base::String& name) { Mutation mutation; for (auto iter = records_.begin(); iter != records_.end();) { if (!MatchesIdentity(*iter, source, name, name)) { @@ -162,7 +162,7 @@ ImperativeAnimationState::Mutation ImperativeAnimationState::Cancel( } ImperativeAnimationState::Mutation ImperativeAnimationState::Finish( - Source source, const base::String& name) { + ImperativeAnimationSource source, const base::String& name) { Mutation mutation; for (auto iter = records_.begin(); iter != records_.end();) { if (!MatchesIdentity(*iter, source, name, name)) { diff --git a/core/renderer/dom/imperative_animation_state.h b/core/renderer/dom/imperative_animation_state.h index c0853fe80c..18685f25b3 100644 --- a/core/renderer/dom/imperative_animation_state.h +++ b/core/renderer/dom/imperative_animation_state.h @@ -5,12 +5,11 @@ #ifndef CORE_RENDERER_DOM_IMPERATIVE_ANIMATION_STATE_H_ #define CORE_RENDERER_DOM_IMPERATIVE_ANIMATION_STATE_H_ -#include - #include "base/include/value/base_string.h" #include "base/include/vector.h" #include "core/renderer/css/css_property.h" #include "core/renderer/css/css_property_bitset.h" +#include "core/renderer/dom/imperative_animation_source.h" #include "core/renderer/starlight/style/css_type.h" namespace lynx { @@ -24,25 +23,22 @@ class CSSKeyframesToken; class ImperativeAnimationState { public: - enum class Source : uint8_t { - kAnimate, - kAnimateV2, - }; - struct Mutation { CSSIDBitset cleanup_properties; base::Vector keyframes_to_remove; }; - Mutation RecordStart(Source source, const base::String& js_name, + Mutation RecordStart(ImperativeAnimationSource source, + const base::String& js_name, const base::String& animation_name, bool owns_generated_keyframe, const StyleMap& timing_styles, CSSKeyframesToken* keyframes_token); - void UpdatePlayState(Source source, const base::String& name, - const StyleMap& timing_styles, bool paused); - Mutation Cancel(Source source, const base::String& name); - Mutation Finish(Source source, const base::String& name); + void UpdatePlayState(ImperativeAnimationSource source, + const base::String& name, const StyleMap& timing_styles, + bool paused); + Mutation Cancel(ImperativeAnimationSource source, const base::String& name); + Mutation Finish(ImperativeAnimationSource source, const base::String& name); Mutation ClearForStyleAnimationUpdate(); Mutation Clear(); @@ -56,7 +52,7 @@ class ImperativeAnimationState { private: struct Record { - Source source{Source::kAnimate}; + ImperativeAnimationSource source{ImperativeAnimationSource::kAnimate}; base::String js_name; base::String animation_name; StyleMap timing_styles; @@ -67,10 +63,12 @@ class ImperativeAnimationState { }; static bool MatchesName(const Record& record, const base::String& name); - static bool MatchesIdentity(const Record& record, Source source, + static bool MatchesIdentity(const Record& record, + ImperativeAnimationSource source, const base::String& js_name, const base::String& animation_name); - static bool ShouldReplaceOnStart(const Record& record, Source source, + static bool ShouldReplaceOnStart(const Record& record, + ImperativeAnimationSource source, const base::String& js_name, const base::String& animation_name); diff --git a/core/renderer/dom/imperative_animation_state_unittest.cc b/core/renderer/dom/imperative_animation_state_unittest.cc index 19adfa5440..259f87aa1a 100644 --- a/core/renderer/dom/imperative_animation_state_unittest.cc +++ b/core/renderer/dom/imperative_animation_state_unittest.cc @@ -17,7 +17,7 @@ namespace tasm { namespace testing { namespace { -using AnimationSource = ImperativeAnimationState::Source; +using AnimationSource = ImperativeAnimationSource; StyleMap MakeTimingStyles(const char* animation_name, starlight::AnimationFillModeType fill_mode =