/src/serenity/Userland/Libraries/LibWeb/Animations/KeyframeEffect.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023-2024, Matthew Olsson <mattco@serenityos.org> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #pragma once |
8 | | |
9 | | #include <AK/Optional.h> |
10 | | #include <AK/RedBlackTree.h> |
11 | | #include <LibWeb/Animations/AnimationEffect.h> |
12 | | #include <LibWeb/Bindings/KeyframeEffectPrototype.h> |
13 | | #include <LibWeb/Bindings/PlatformObject.h> |
14 | | #include <LibWeb/CSS/CSSStyleValue.h> |
15 | | #include <LibWeb/CSS/PropertyID.h> |
16 | | #include <LibWeb/CSS/Selector.h> |
17 | | |
18 | | namespace Web::Animations { |
19 | | |
20 | | using EasingValue = Variant<String, NonnullRefPtr<CSS::CSSStyleValue const>>; |
21 | | |
22 | | // https://www.w3.org/TR/web-animations-1/#the-keyframeeffectoptions-dictionary |
23 | | struct KeyframeEffectOptions : public EffectTiming { |
24 | | Bindings::CompositeOperation composite { Bindings::CompositeOperation::Replace }; |
25 | | Optional<String> pseudo_element {}; |
26 | | }; |
27 | | |
28 | | // https://www.w3.org/TR/web-animations-1/#dictdef-basepropertyindexedkeyframe |
29 | | // Note: This is an intermediate structure used only when parsing Keyframes provided by the caller in a slightly |
30 | | // different format. It is converted to BaseKeyframe, which is why it doesn't need to store the parsed properties |
31 | | struct BasePropertyIndexedKeyframe { |
32 | | Variant<Optional<double>, Vector<Optional<double>>> offset { Vector<Optional<double>> {} }; |
33 | | Variant<EasingValue, Vector<EasingValue>> easing { Vector<EasingValue> {} }; |
34 | | Variant<Bindings::CompositeOperationOrAuto, Vector<Bindings::CompositeOperationOrAuto>> composite { Vector<Bindings::CompositeOperationOrAuto> {} }; |
35 | | |
36 | | HashMap<String, Vector<String>> properties {}; |
37 | | }; |
38 | | |
39 | | // https://www.w3.org/TR/web-animations-1/#dictdef-basekeyframe |
40 | | struct BaseKeyframe { |
41 | | using UnparsedProperties = HashMap<String, String>; |
42 | | using ParsedProperties = HashMap<CSS::PropertyID, NonnullRefPtr<CSS::CSSStyleValue const>>; |
43 | | |
44 | | Optional<double> offset {}; |
45 | | EasingValue easing { "linear"_string }; |
46 | | Bindings::CompositeOperationOrAuto composite { Bindings::CompositeOperationOrAuto::Auto }; |
47 | | |
48 | | Optional<double> computed_offset {}; |
49 | | |
50 | | Variant<UnparsedProperties, ParsedProperties> properties { UnparsedProperties {} }; |
51 | | |
52 | 0 | UnparsedProperties& unparsed_properties() { return properties.get<UnparsedProperties>(); } |
53 | 0 | ParsedProperties& parsed_properties() { return properties.get<ParsedProperties>(); } |
54 | | }; |
55 | | |
56 | | // https://www.w3.org/TR/web-animations-1/#the-keyframeeffect-interface |
57 | | class KeyframeEffect : public AnimationEffect { |
58 | | WEB_PLATFORM_OBJECT(KeyframeEffect, AnimationEffect); |
59 | | JS_DECLARE_ALLOCATOR(KeyframeEffect); |
60 | | |
61 | | public: |
62 | | constexpr static double AnimationKeyFrameKeyScaleFactor = 1000.0; // 0..100000 |
63 | | |
64 | | struct KeyFrameSet : public RefCounted<KeyFrameSet> { |
65 | | struct UseInitial { }; |
66 | | struct ResolvedKeyFrame { |
67 | | // These CSSStyleValue properties can be unresolved, as they may be generated from a @keyframes rule, well |
68 | | // before they are applied to an element |
69 | | HashMap<CSS::PropertyID, Variant<UseInitial, NonnullRefPtr<CSS::CSSStyleValue const>>> properties {}; |
70 | | }; |
71 | | RedBlackTree<u64, ResolvedKeyFrame> keyframes_by_key; |
72 | | }; |
73 | | static void generate_initial_and_final_frames(RefPtr<KeyFrameSet>, HashTable<CSS::PropertyID> const& animated_properties); |
74 | | |
75 | | static int composite_order(JS::NonnullGCPtr<KeyframeEffect>, JS::NonnullGCPtr<KeyframeEffect>); |
76 | | |
77 | | static JS::NonnullGCPtr<KeyframeEffect> create(JS::Realm&); |
78 | | |
79 | | static WebIDL::ExceptionOr<JS::NonnullGCPtr<KeyframeEffect>> construct_impl( |
80 | | JS::Realm&, |
81 | | JS::Handle<DOM::Element> const& target, |
82 | | Optional<JS::Handle<JS::Object>> const& keyframes, |
83 | | Variant<double, KeyframeEffectOptions> options = KeyframeEffectOptions {}); |
84 | | |
85 | | static WebIDL::ExceptionOr<JS::NonnullGCPtr<KeyframeEffect>> construct_impl(JS::Realm&, JS::NonnullGCPtr<KeyframeEffect> source); |
86 | | |
87 | 0 | DOM::Element* target() const override { return m_target_element; } |
88 | | void set_target(DOM::Element* target); |
89 | | |
90 | | // JS bindings |
91 | | Optional<String> pseudo_element() const; |
92 | | WebIDL::ExceptionOr<void> set_pseudo_element(Optional<String>); |
93 | | |
94 | | Optional<CSS::Selector::PseudoElement::Type> pseudo_element_type() const; |
95 | 0 | void set_pseudo_element(Optional<CSS::Selector::PseudoElement> pseudo_element) { m_target_pseudo_selector = pseudo_element; } |
96 | | |
97 | 0 | Bindings::CompositeOperation composite() const { return m_composite; } |
98 | 0 | void set_composite(Bindings::CompositeOperation value) { m_composite = value; } |
99 | | |
100 | | WebIDL::ExceptionOr<JS::MarkedVector<JS::Object*>> get_keyframes(); |
101 | | WebIDL::ExceptionOr<void> set_keyframes(Optional<JS::Handle<JS::Object>> const&); |
102 | | |
103 | 0 | KeyFrameSet const* key_frame_set() { return m_key_frame_set; } |
104 | 0 | void set_key_frame_set(RefPtr<KeyFrameSet const> key_frame_set) { m_key_frame_set = key_frame_set; } |
105 | | |
106 | 0 | virtual bool is_keyframe_effect() const override { return true; } |
107 | | |
108 | | virtual void update_style_properties() override; |
109 | | |
110 | 0 | Optional<CSS::AnimationPlayState> last_css_animation_play_state() const { return m_last_css_animation_play_state; } |
111 | 0 | void set_last_css_animation_play_state(CSS::AnimationPlayState state) { m_last_css_animation_play_state = state; } |
112 | | |
113 | | private: |
114 | | KeyframeEffect(JS::Realm&); |
115 | 0 | virtual ~KeyframeEffect() override = default; |
116 | | |
117 | | virtual void initialize(JS::Realm&) override; |
118 | | virtual void visit_edges(Cell::Visitor&) override; |
119 | | |
120 | | // https://www.w3.org/TR/web-animations-1/#effect-target-target-element |
121 | | JS::GCPtr<DOM::Element> m_target_element {}; |
122 | | |
123 | | // https://www.w3.org/TR/web-animations-1/#dom-keyframeeffect-pseudoelement |
124 | | Optional<CSS::Selector::PseudoElement> m_target_pseudo_selector {}; |
125 | | |
126 | | // https://www.w3.org/TR/web-animations-1/#dom-keyframeeffect-composite |
127 | | Bindings::CompositeOperation m_composite { Bindings::CompositeOperation::Replace }; |
128 | | |
129 | | // https://www.w3.org/TR/web-animations-1/#keyframe |
130 | | Vector<BaseKeyframe> m_keyframes {}; |
131 | | |
132 | | // A cached version of m_keyframes suitable for returning from get_keyframes() |
133 | | Vector<JS::NonnullGCPtr<JS::Object>> m_keyframe_objects {}; |
134 | | |
135 | | RefPtr<KeyFrameSet const> m_key_frame_set {}; |
136 | | |
137 | | Optional<CSS::AnimationPlayState> m_last_css_animation_play_state; |
138 | | }; |
139 | | |
140 | | } |