LibWeb: Support triggering multiple animations per animation property

We also now use the computed (rather than cascaded) values when
triggering animations.
This commit is contained in:
Callum Law 2025-10-26 18:44:31 +13:00 committed by Sam Atkins
parent 18477b0d84
commit 84762021b8
11 changed files with 322 additions and 231 deletions

View File

@ -246,10 +246,11 @@ void Animatable::visit_edges(JS::Cell::Visitor& visitor)
{
auto& impl = ensure_impl();
visitor.visit(impl.associated_animations);
for (auto const& cached_animation_source : impl.cached_animation_name_source)
visitor.visit(cached_animation_source);
for (auto const& cached_animation_name : impl.cached_animation_name_animation)
visitor.visit(cached_animation_name);
for (auto const& css_animation : impl.css_defined_animations) {
if (css_animation)
visitor.visit(*css_animation);
}
for (auto const& transition : impl.transitions) {
if (transition) {
visitor.visit(transition->cached_transition_property_source);
@ -258,61 +259,21 @@ void Animatable::visit_edges(JS::Cell::Visitor& visitor)
}
}
GC::Ptr<CSS::CSSStyleDeclaration const> Animatable::cached_animation_name_source(Optional<CSS::PseudoElement> pseudo_element) const
{
if (!m_impl)
return {};
auto& impl = *m_impl;
if (pseudo_element.has_value()) {
if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) {
return {};
}
return impl.cached_animation_name_source[to_underlying(pseudo_element.value()) + 1];
}
return impl.cached_animation_name_source[0];
}
void Animatable::set_cached_animation_name_source(GC::Ptr<CSS::CSSStyleDeclaration const> value, Optional<CSS::PseudoElement> pseudo_element)
HashMap<FlyString, GC::Ref<Animation>>* Animatable::css_defined_animations(Optional<CSS::PseudoElement> pseudo_element)
{
auto& impl = ensure_impl();
if (pseudo_element.has_value()) {
if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) {
return;
}
impl.cached_animation_name_source[to_underlying(pseudo_element.value()) + 1] = value;
} else {
impl.cached_animation_name_source[0] = value;
}
}
GC::Ptr<Animations::Animation> Animatable::cached_animation_name_animation(Optional<CSS::PseudoElement> pseudo_element) const
{
if (!m_impl)
return {};
auto& impl = *m_impl;
if (pseudo_element.has_value() && !CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value()))
return nullptr;
if (pseudo_element.has_value()) {
if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) {
return {};
}
auto index = pseudo_element
.map([](CSS::PseudoElement pseudo_element_value) { return to_underlying(pseudo_element_value) + 1; })
.value_or(0);
return impl.cached_animation_name_animation[to_underlying(pseudo_element.value()) + 1];
}
return impl.cached_animation_name_animation[0];
}
if (!impl.css_defined_animations[index])
impl.css_defined_animations[index] = make<HashMap<FlyString, GC::Ref<Animation>>>();
void Animatable::set_cached_animation_name_animation(GC::Ptr<Animations::Animation> value, Optional<CSS::PseudoElement> pseudo_element)
{
auto& impl = ensure_impl();
if (pseudo_element.has_value()) {
if (!CSS::Selector::PseudoElementSelector::is_known_pseudo_element_type(pseudo_element.value())) {
return;
}
impl.cached_animation_name_animation[to_underlying(pseudo_element.value()) + 1] = value;
} else {
impl.cached_animation_name_animation[0] = value;
}
return impl.css_defined_animations[index];
}
GC::Ptr<CSS::CSSStyleDeclaration const> Animatable::cached_transition_property_source(Optional<CSS::PseudoElement> pseudo_element) const

View File

@ -52,11 +52,9 @@ public:
void associate_with_animation(GC::Ref<Animation>);
void disassociate_with_animation(GC::Ref<Animation>);
GC::Ptr<CSS::CSSStyleDeclaration const> cached_animation_name_source(Optional<CSS::PseudoElement>) const;
void set_cached_animation_name_source(GC::Ptr<CSS::CSSStyleDeclaration const> value, Optional<CSS::PseudoElement>);
GC::Ptr<Animations::Animation> cached_animation_name_animation(Optional<CSS::PseudoElement>) const;
void set_cached_animation_name_animation(GC::Ptr<Animations::Animation> value, Optional<CSS::PseudoElement>);
HashMap<FlyString, GC::Ref<Animation>>* css_defined_animations(Optional<CSS::PseudoElement>);
void add_css_animation(FlyString name, Optional<CSS::PseudoElement>, GC::Ref<Animation>);
void remove_css_animation(FlyString name, Optional<CSS::PseudoElement>);
GC::Ptr<CSS::CSSStyleDeclaration const> cached_transition_property_source(Optional<CSS::PseudoElement>) const;
void set_cached_transition_property_source(Optional<CSS::PseudoElement>, GC::Ptr<CSS::CSSStyleDeclaration const> value);
@ -80,9 +78,7 @@ private:
Vector<GC::Ref<Animation>> associated_animations;
bool is_sorted_by_composite_order { true };
Array<GC::Ptr<CSS::CSSStyleDeclaration const>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> cached_animation_name_source;
Array<GC::Ptr<Animation>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> cached_animation_name_animation;
mutable Array<OwnPtr<HashMap<FlyString, GC::Ref<Animation>>>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> css_defined_animations;
mutable Array<OwnPtr<Transition>, to_underlying(CSS::PseudoElement::KnownPseudoElementCount) + 1> transitions;
~Impl();

View File

@ -37,6 +37,7 @@
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
#include <LibWeb/CSS/StyleValues/TextUnderlinePositionStyleValue.h>
#include <LibWeb/CSS/StyleValues/TimeStyleValue.h>
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
#include <LibWeb/Layout/BlockContainer.h>
#include <LibWeb/Layout/Node.h>
@ -53,7 +54,6 @@ ComputedProperties::~ComputedProperties() = default;
void ComputedProperties::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_animation_name_source);
visitor.visit(m_transition_property_source);
}
@ -1934,6 +1934,108 @@ Optional<FlyString> ComputedProperties::view_transition_name() const
return {};
}
Vector<ComputedProperties::AnimationProperties> ComputedProperties::animations() const
{
// CSS Animations are defined by binding keyframes to an element using the animation-* properties. These list-valued
// properties, which are all longhands of the animation shorthand, form a coordinating list property group with
// animation-name as the coordinating list base property and each item in the coordinated value list defining the
// properties of a single animation effect.
auto const& coordinated_properties = assemble_coordinated_value_list(
PropertyID::AnimationName,
{ PropertyID::AnimationDuration,
PropertyID::AnimationTimingFunction,
PropertyID::AnimationIterationCount,
PropertyID::AnimationDirection,
PropertyID::AnimationPlayState,
PropertyID::AnimationDelay,
PropertyID::AnimationFillMode,
PropertyID::AnimationComposition,
PropertyID::AnimationName });
Vector<AnimationProperties> animations;
for (size_t i = 0; i < coordinated_properties.get(PropertyID::AnimationName)->size(); i++) {
// https://drafts.csswg.org/css-animations-1/#propdef-animation-name
// none: No keyframes are specified at all, so there will be no animation. Any other animations properties
// specified for this animation have no effect.
if (coordinated_properties.get(PropertyID::AnimationName).value()[i]->to_keyword() == Keyword::None)
continue;
auto animation_name_style_value = coordinated_properties.get(PropertyID::AnimationName).value()[i];
auto animation_duration_style_value = coordinated_properties.get(PropertyID::AnimationDuration).value()[i];
auto animation_timing_function_style_value = coordinated_properties.get(PropertyID::AnimationTimingFunction).value()[i];
auto animation_iteration_count_style_value = coordinated_properties.get(PropertyID::AnimationIterationCount).value()[i];
auto animation_direction_style_value = coordinated_properties.get(PropertyID::AnimationDirection).value()[i];
auto animation_play_state_style_value = coordinated_properties.get(PropertyID::AnimationPlayState).value()[i];
auto animation_delay_style_value = coordinated_properties.get(PropertyID::AnimationDelay).value()[i];
auto animation_fill_mode_style_value = coordinated_properties.get(PropertyID::AnimationFillMode).value()[i];
auto animation_composition_style_value = coordinated_properties.get(PropertyID::AnimationComposition).value()[i];
auto duration = [&] {
if (animation_duration_style_value->is_time())
return animation_duration_style_value->as_time().time().to_milliseconds();
if (animation_duration_style_value->is_calculated())
return animation_duration_style_value->as_calculated().resolve_time({}).value().to_milliseconds();
VERIFY_NOT_REACHED();
}();
auto timing_function = EasingFunction::from_style_value(animation_timing_function_style_value);
auto iteration_count = [&] {
if (animation_iteration_count_style_value->to_keyword() == Keyword::Infinite)
return AK::Infinity<double>;
if (animation_iteration_count_style_value->is_number())
return animation_iteration_count_style_value->as_number().number();
if (animation_iteration_count_style_value->is_calculated())
return animation_iteration_count_style_value->as_calculated().resolve_number({}).value();
VERIFY_NOT_REACHED();
}();
auto direction = keyword_to_animation_direction(animation_direction_style_value->to_keyword()).value();
auto play_state = keyword_to_animation_play_state(animation_play_state_style_value->to_keyword()).value();
auto delay = [&] {
if (animation_delay_style_value->is_time())
return animation_delay_style_value->as_time().time().to_milliseconds();
if (animation_delay_style_value->is_calculated())
return animation_delay_style_value->as_calculated().resolve_time({}).value().to_milliseconds();
VERIFY_NOT_REACHED();
}();
auto fill_mode = keyword_to_animation_fill_mode(animation_fill_mode_style_value->to_keyword()).value();
auto composition = keyword_to_animation_composition(animation_composition_style_value->to_keyword()).value();
auto name = [&] {
if (animation_name_style_value->is_custom_ident())
return animation_name_style_value->as_custom_ident().custom_ident();
if (animation_name_style_value->is_string())
return animation_name_style_value->as_string().string_value();
VERIFY_NOT_REACHED();
}();
animations.append(AnimationProperties {
.duration = duration,
.timing_function = timing_function,
.iteration_count = iteration_count,
.direction = direction,
.play_state = play_state,
.delay = delay,
.fill_mode = fill_mode,
.composition = composition,
.name = name,
});
}
return animations;
}
MaskType ComputedProperties::mask_type() const
{
auto const& value = property(PropertyID::MaskType);

View File

@ -16,6 +16,7 @@
#include <LibGfx/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibWeb/CSS/ComputedValues.h>
#include <LibWeb/CSS/EasingFunction.h>
#include <LibWeb/CSS/LengthBox.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/PseudoClass.h>
@ -68,9 +69,6 @@ public:
StyleValue const& property(PropertyID, WithAnimationsApplied = WithAnimationsApplied::Yes) const;
void revert_property(PropertyID, ComputedProperties const& style_for_revert);
GC::Ptr<CSSStyleDeclaration const> animation_name_source() const { return m_animation_name_source; }
void set_animation_name_source(GC::Ptr<CSSStyleDeclaration const> declaration) { m_animation_name_source = declaration; }
GC::Ptr<CSSStyleDeclaration const> transition_property_source() const { return m_transition_property_source; }
void set_transition_property_source(GC::Ptr<CSSStyleDeclaration const> declaration) { m_transition_property_source = declaration; }
@ -187,6 +185,18 @@ public:
ContainerType container_type() const;
MixBlendMode mix_blend_mode() const;
Optional<FlyString> view_transition_name() const;
struct AnimationProperties {
Variant<double, String> duration;
EasingFunction timing_function;
double iteration_count;
AnimationDirection direction;
AnimationPlayState play_state;
double delay;
AnimationFillMode fill_mode;
AnimationComposition composition;
FlyString name;
};
Vector<AnimationProperties> animations() const;
Display display_before_box_type_transformation() const;
void set_display_before_box_type_transformation(Display value);
@ -274,7 +284,6 @@ private:
Overflow overflow(PropertyID) const;
Vector<ShadowData> shadow(PropertyID, Layout::Node const&) const;
GC::Ptr<CSSStyleDeclaration const> m_animation_name_source;
GC::Ptr<CSSStyleDeclaration const> m_transition_property_source;
Array<RefPtr<StyleValue const>, number_of_longhand_properties> m_property_values;

View File

@ -1177,103 +1177,92 @@ void StyleComputer::collect_animation_into(DOM::AbstractElement abstract_element
}
}
static void apply_animation_properties(DOM::Document& document, CascadedProperties& cascaded_properties, Animations::Animation& animation)
static void apply_animation_properties(DOM::Document const& document, ComputedProperties::AnimationProperties const& animation_properties, Animations::Animation& animation)
{
if (!animation.effect())
return;
VERIFY(animation.effect());
auto& effect = as<Animations::KeyframeEffect>(*animation.effect());
Optional<CSS::Time> duration;
if (auto duration_value = cascaded_properties.property(PropertyID::AnimationDuration); duration_value) {
if (duration_value->is_time()) {
duration = duration_value->as_time().time();
} else if (duration_value->is_keyword() && duration_value->as_keyword().keyword() == Keyword::Auto) {
// We use empty optional to represent "auto".
duration = {};
} else if (duration_value->is_calculated() && duration_value->as_calculated().resolves_to_time()) {
auto resolved_time = duration_value->as_calculated().resolve_time({});
if (resolved_time.has_value()) {
duration = resolved_time.value();
}
}
}
effect.set_iteration_duration(animation_properties.duration);
effect.set_start_delay(animation_properties.delay);
effect.set_iteration_count(animation_properties.iteration_count);
effect.set_timing_function(animation_properties.timing_function);
effect.set_fill_mode(Animations::css_fill_mode_to_bindings_fill_mode(animation_properties.fill_mode));
effect.set_playback_direction(Animations::css_animation_direction_to_bindings_playback_direction(animation_properties.direction));
effect.set_composite(Animations::css_animation_composition_to_bindings_composite_operation(animation_properties.composition));
auto delay = Time::make_seconds(0);
if (auto delay_value = cascaded_properties.property(PropertyID::AnimationDelay); delay_value) {
if (delay_value->is_time()) {
delay = delay_value->as_time().time();
} else if (delay_value->is_calculated() && delay_value->as_calculated().resolves_to_time()) {
auto resolved_time = delay_value->as_calculated().resolve_time({});
if (resolved_time.has_value()) {
delay = resolved_time.value();
}
}
}
double iteration_count = 1.0;
if (auto iteration_count_value = cascaded_properties.property(PropertyID::AnimationIterationCount); iteration_count_value) {
if (iteration_count_value->is_keyword() && iteration_count_value->to_keyword() == Keyword::Infinite)
iteration_count = HUGE_VAL;
else if (iteration_count_value->is_number())
iteration_count = iteration_count_value->as_number().number();
else if (iteration_count_value->is_calculated() && iteration_count_value->as_calculated().resolves_to_number()) {
auto resolved_number = iteration_count_value->as_calculated().resolve_number({});
if (resolved_number.has_value()) {
iteration_count = resolved_number.value();
}
}
}
CSS::AnimationFillMode fill_mode { CSS::AnimationFillMode::None };
if (auto fill_mode_property = cascaded_properties.property(PropertyID::AnimationFillMode); fill_mode_property && fill_mode_property->is_keyword()) {
if (auto fill_mode_value = keyword_to_animation_fill_mode(fill_mode_property->to_keyword()); fill_mode_value.has_value())
fill_mode = *fill_mode_value;
}
CSS::AnimationDirection direction { CSS::AnimationDirection::Normal };
if (auto direction_property = cascaded_properties.property(PropertyID::AnimationDirection); direction_property && direction_property->is_keyword()) {
if (auto direction_value = keyword_to_animation_direction(direction_property->to_keyword()); direction_value.has_value())
direction = *direction_value;
}
CSS::AnimationPlayState play_state { CSS::AnimationPlayState::Running };
if (auto play_state_property = cascaded_properties.property(PropertyID::AnimationPlayState); play_state_property && play_state_property->is_keyword()) {
if (auto play_state_value = keyword_to_animation_play_state(play_state_property->to_keyword()); play_state_value.has_value())
play_state = *play_state_value;
}
EasingFunction timing_function = EasingFunction::ease();
if (auto timing_property = cascaded_properties.property(PropertyID::AnimationTimingFunction); timing_property && (timing_property->is_easing() || (timing_property->is_keyword() && !timing_property->is_css_wide_keyword())))
timing_function = EasingFunction::from_style_value(timing_property.release_nonnull());
AnimationComposition animation_composition { AnimationComposition::Replace };
if (auto composite_property = cascaded_properties.property(PropertyID::AnimationComposition); composite_property) {
if (auto animation_composition_value = keyword_to_animation_composition(composite_property->to_keyword()); animation_composition_value.has_value())
animation_composition = *animation_composition_value;
}
auto iteration_duration = duration.has_value()
? Variant<double, String> { duration.release_value().to_milliseconds() }
: "auto"_string;
effect.set_iteration_duration(iteration_duration);
effect.set_start_delay(delay.to_milliseconds());
effect.set_iteration_count(iteration_count);
effect.set_timing_function(move(timing_function));
effect.set_fill_mode(Animations::css_fill_mode_to_bindings_fill_mode(fill_mode));
effect.set_playback_direction(Animations::css_animation_direction_to_bindings_playback_direction(direction));
effect.set_composite(Animations::css_animation_composition_to_bindings_composite_operation(animation_composition));
if (play_state != effect.last_css_animation_play_state()) {
if (play_state == CSS::AnimationPlayState::Running && animation.play_state() != Bindings::AnimationPlayState::Running) {
if (animation_properties.play_state != effect.last_css_animation_play_state()) {
if (animation_properties.play_state == CSS::AnimationPlayState::Running && animation.play_state() != Bindings::AnimationPlayState::Running) {
HTML::TemporaryExecutionContext context(document.realm());
animation.play().release_value_but_fixme_should_propagate_errors();
} else if (play_state == CSS::AnimationPlayState::Paused && animation.play_state() != Bindings::AnimationPlayState::Paused) {
} else if (animation_properties.play_state == CSS::AnimationPlayState::Paused && animation.play_state() != Bindings::AnimationPlayState::Paused) {
HTML::TemporaryExecutionContext context(document.realm());
animation.pause().release_value_but_fixme_should_propagate_errors();
}
effect.set_last_css_animation_play_state(play_state);
effect.set_last_css_animation_play_state(animation_properties.play_state);
}
}
// https://drafts.csswg.org/css-animations-1/#animations
void StyleComputer::process_animation_definitions(ComputedProperties const& computed_properties, DOM::AbstractElement& abstract_element) const
{
auto const& animation_definitions = computed_properties.animations();
// FIXME: Add some animation helpers to AbstractElement once pseudo-elements are animatable.
auto& element = abstract_element.element();
auto const& pseudo_element = abstract_element.pseudo_element();
auto& document = abstract_element.document();
auto* element_animations = element.css_defined_animations(pseudo_element);
// If we have a nullptr for element_animations it means that the pseudo element was invalid and thus we shouldn't apply animations
if (!element_animations)
return;
HashTable<FlyString> defined_animation_names;
for (auto const& animation_properties : animation_definitions) {
defined_animation_names.set(animation_properties.name);
// Changes to the values of animation properties while the animation is running apply as if the animation had
// those values from when it began
if (auto const& existing_animation = element_animations->get(animation_properties.name); existing_animation.has_value()) {
apply_animation_properties(document, animation_properties, existing_animation.value());
return;
}
// An animation applies to an element if its name appears as one of the identifiers in the computed value of the
// animation-name property and the animation uses a valid @keyframes rule
auto animation = CSSAnimation::create(document.realm());
animation->set_id(animation_properties.name);
animation->set_timeline(document.timeline());
animation->set_owning_element(element);
auto effect = Animations::KeyframeEffect::create(document.realm());
animation->set_effect(effect);
apply_animation_properties(document, animation_properties, animation);
if (pseudo_element.has_value())
effect->set_pseudo_element(Selector::PseudoElementSelector { pseudo_element.value() });
if (auto const* rule_cache = rule_cache_for_cascade_origin(CascadeOrigin::Author, {}, {})) {
if (auto keyframe_set = rule_cache->rules_by_animation_keyframes.get(animation_properties.name); keyframe_set.has_value())
effect->set_key_frame_set(keyframe_set.value());
}
effect->set_target(&element);
element_animations->set(animation_properties.name, animation);
}
// Once an animation has started it continues until it ends or the animation-name is removed
for (auto const& existing_animation_name : element_animations->keys()) {
if (defined_animation_names.contains(existing_animation_name))
continue;
element_animations->get(existing_animation_name).value()->cancel(Animations::Animation::ShouldInvalidate::No);
element_animations->remove(existing_animation_name);
}
}
@ -2569,9 +2558,6 @@ GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::AbstractEleme
if (animated_value.has_value())
computed_style->set_animated_property(property_id, animated_value.value(), inherited);
if (property_id == PropertyID::AnimationName) {
computed_style->set_animation_name_source(cascaded_properties.property_source(property_id));
}
if (property_id == PropertyID::TransitionProperty) {
computed_style->set_transition_property_source(cascaded_properties.property_source(property_id));
}
@ -2589,6 +2575,9 @@ GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::AbstractEleme
// 4. Convert properties into their computed forms
compute_property_values(computed_style, abstract_element);
// 5. Add or modify CSS-defined animations
process_animation_definitions(computed_style, abstract_element);
// FIXME: Support multiple entries for `animation` properties
// Animation declarations [css-animations-2]
auto animation_name = [&]() -> Optional<String> {
@ -2600,76 +2589,30 @@ GC::Ref<ComputedProperties> StyleComputer::compute_properties(DOM::AbstractEleme
return animation_name.to_string(SerializationMode::Normal);
}();
// FIXME: Add some animation helpers to AbstractElement once pseudo-elements are animatable.
auto& element = abstract_element.element();
auto pseudo_element = abstract_element.pseudo_element();
if (animation_name.has_value()) {
if (auto source_declaration = computed_style->animation_name_source()) {
auto& realm = element.realm();
if (source_declaration != element.cached_animation_name_source(pseudo_element)) {
// This animation name is new, so we need to create a new animation for it.
if (auto existing_animation = element.cached_animation_name_animation(pseudo_element))
existing_animation->cancel(Animations::Animation::ShouldInvalidate::No);
element.set_cached_animation_name_source(source_declaration, pseudo_element);
auto effect = Animations::KeyframeEffect::create(realm);
auto animation = CSSAnimation::create(realm);
animation->set_id(animation_name.release_value());
animation->set_timeline(m_document->timeline());
animation->set_owning_element(element);
animation->set_effect(effect);
apply_animation_properties(m_document, cascaded_properties, animation);
if (pseudo_element.has_value())
effect->set_pseudo_element(Selector::PseudoElementSelector { pseudo_element.value() });
if (auto* rule_cache = rule_cache_for_cascade_origin(CascadeOrigin::Author, {}, {})) {
if (auto keyframe_set = rule_cache->rules_by_animation_keyframes.get(animation->id()); keyframe_set.has_value())
effect->set_key_frame_set(keyframe_set.value());
}
effect->set_target(&element);
element.set_cached_animation_name_animation(animation, pseudo_element);
} else {
// The animation hasn't changed, but some properties of the animation may have
if (auto animation = element.cached_animation_name_animation(pseudo_element); animation)
apply_animation_properties(m_document, cascaded_properties, *animation);
}
}
} else {
// If the element had an existing animation, cancel it
if (auto existing_animation = element.cached_animation_name_animation(pseudo_element)) {
existing_animation->cancel(Animations::Animation::ShouldInvalidate::No);
element.set_cached_animation_name_animation({}, pseudo_element);
element.set_cached_animation_name_source({}, pseudo_element);
}
}
auto animations = element.get_animations_internal(Animations::GetAnimationsOptions { .subtree = false });
auto animations = abstract_element.element().get_animations_internal(Animations::GetAnimationsOptions { .subtree = false });
if (animations.is_exception()) {
dbgln("Error getting animations for element {}", element.debug_description());
dbgln("Error getting animations for element {}", abstract_element.debug_description());
} else {
for (auto& animation : animations.value()) {
if (auto effect = animation->effect(); effect && effect->is_keyframe_effect()) {
auto& keyframe_effect = *static_cast<Animations::KeyframeEffect*>(effect.ptr());
if (keyframe_effect.pseudo_element_type() == pseudo_element)
if (keyframe_effect.pseudo_element_type() == abstract_element.pseudo_element())
collect_animation_into(abstract_element, keyframe_effect, computed_style);
}
}
}
// 5. Run automatic box type transformations
// 6. Run automatic box type transformations
transform_box_type_if_needed(computed_style, abstract_element);
// 6. Apply any property-specific computed value logic
// 7. Apply any property-specific computed value logic
resolve_effective_overflow_values(computed_style);
compute_text_align(computed_style, abstract_element);
// 7. Let the element adjust computed style
element.adjust_computed_style(computed_style);
// 8. Let the element adjust computed style
abstract_element.element().adjust_computed_style(computed_style);
// 8. Transition declarations [css-transitions-1]
// 9. Transition declarations [css-transitions-1]
// Theoretically this should be part of the cascade, but it works with computed values, which we don't have until now.
compute_transitioned_properties(computed_style, abstract_element);
if (auto previous_style = abstract_element.computed_properties()) {

View File

@ -193,6 +193,7 @@ public:
void compute_property_values(ComputedProperties&, Optional<DOM::AbstractElement>) const;
void compute_font(ComputedProperties&, Optional<DOM::AbstractElement>) const;
void process_animation_definitions(ComputedProperties const& computed_properties, DOM::AbstractElement& abstract_element) const;
[[nodiscard]] inline bool should_reject_with_ancestor_filter(Selector const&) const;

View File

@ -4089,9 +4089,10 @@ void Element::play_or_cancel_animations_after_display_property_change()
auto has_display_none_inclusive_ancestor = this->has_inclusive_ancestor_with_display_none();
auto play_or_cancel_depending_on_display = [&](Animations::Animation& animation, Optional<CSS::PseudoElement> pseudo_element) {
auto play_or_cancel_depending_on_display = [&](HashMap<FlyString, GC::Ref<Animations::Animation>>& animations, Optional<CSS::PseudoElement> pseudo_element) {
for (auto& [_, animation] : animations) {
if (has_display_none_inclusive_ancestor) {
animation.cancel();
animation->cancel();
} else {
auto play_state { CSS::AnimationPlayState::Running };
if (auto play_state_property = cascaded_properties(pseudo_element)->property(CSS::PropertyID::AnimationPlayState);
@ -4103,20 +4104,20 @@ void Element::play_or_cancel_animations_after_display_property_change()
}
if (play_state == CSS::AnimationPlayState::Running) {
HTML::TemporaryExecutionContext context(realm());
animation.play().release_value_but_fixme_should_propagate_errors();
animation->play().release_value_but_fixme_should_propagate_errors();
} else if (play_state == CSS::AnimationPlayState::Paused) {
HTML::TemporaryExecutionContext context(realm());
animation.pause().release_value_but_fixme_should_propagate_errors();
animation->pause().release_value_but_fixme_should_propagate_errors();
}
}
}
};
if (auto animation = cached_animation_name_animation({}))
play_or_cancel_depending_on_display(*animation, {});
play_or_cancel_depending_on_display(*css_defined_animations({}), {});
for (auto i = 0; i < to_underlying(CSS::PseudoElement::KnownPseudoElementCount); i++) {
auto pseudo_element = static_cast<CSS::PseudoElement>(i);
if (auto animation = cached_animation_name_animation(pseudo_element))
play_or_cancel_depending_on_display(*animation, pseudo_element);
play_or_cancel_depending_on_display(*css_defined_animations(pseudo_element), pseudo_element);
}
}

View File

@ -0,0 +1,2 @@
linear
2

View File

@ -0,0 +1,4 @@
foo
1000
bar
2000

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<style>
@keyframes foo {
from {
}
to {
}
}
#bar {
animation-timing-function: linear;
}
#baz {
animation: foo 2s;
animation-iteration-count: calc(sibling-index() + sign(1em - 1px));
animation-timing-function: inherit;
}
</style>
<body>
<div id="bar"><div id="baz"></div></div>
<script src="../include.js"></script>
<script>
test(() => {
println(document.getAnimations()[0].effect.getComputedTiming().easing);
println(document.getAnimations()[0].effect.getComputedTiming().iterations);
});
</script>
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<style>
@keyframes foo {
from {
top: 0px;
}
to {
top: 10px;
}
}
@keyframes bar {
from {
left: 0px;
}
to {
left: 10px;
}
}
#baz {
animation: foo 1s, bar 2s;
}
</style>
<body>
<div id="baz"></div>
<script src="../include.js"></script>
<script>
test(() => {
for (const animation of document.getAnimations()) {
println(animation.animationName);
println(animation.effect.getTiming().duration);
}
});
</script>
</body>
</html>