mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 00:19:53 +01:00
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:
parent
18477b0d84
commit
84762021b8
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -4089,34 +4089,35 @@ 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) {
|
||||
if (has_display_none_inclusive_ancestor) {
|
||||
animation.cancel();
|
||||
} else {
|
||||
auto play_state { CSS::AnimationPlayState::Running };
|
||||
if (auto play_state_property = cascaded_properties(pseudo_element)->property(CSS::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;
|
||||
}
|
||||
if (play_state == CSS::AnimationPlayState::Running) {
|
||||
HTML::TemporaryExecutionContext context(realm());
|
||||
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();
|
||||
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();
|
||||
} else {
|
||||
auto play_state { CSS::AnimationPlayState::Running };
|
||||
if (auto play_state_property = cascaded_properties(pseudo_element)->property(CSS::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;
|
||||
}
|
||||
if (play_state == CSS::AnimationPlayState::Running) {
|
||||
HTML::TemporaryExecutionContext context(realm());
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
linear
|
||||
2
|
||||
4
Tests/LibWeb/Text/expected/css/multiple-animations.txt
Normal file
4
Tests/LibWeb/Text/expected/css/multiple-animations.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
foo
|
||||
1000
|
||||
bar
|
||||
2000
|
||||
|
|
@ -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>
|
||||
40
Tests/LibWeb/Text/input/css/multiple-animations.html
Normal file
40
Tests/LibWeb/Text/input/css/multiple-animations.html
Normal 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>
|
||||
Loading…
Reference in New Issue
Block a user