/src/serenity/Userland/Libraries/LibWeb/Animations/Animation.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 <LibJS/Runtime/PromiseCapability.h> |
10 | | #include <LibWeb/Bindings/AnimationPrototype.h> |
11 | | #include <LibWeb/DOM/EventTarget.h> |
12 | | |
13 | | namespace Web::Animations { |
14 | | |
15 | | // Sorted by composite order: |
16 | | // https://www.w3.org/TR/css-animations-2/#animation-composite-order |
17 | | enum class AnimationClass { |
18 | | CSSAnimationWithOwningElement, |
19 | | CSSTransition, |
20 | | CSSAnimationWithoutOwningElement, |
21 | | None, |
22 | | }; |
23 | | |
24 | | // https://www.w3.org/TR/web-animations-1/#the-animation-interface |
25 | | class Animation : public DOM::EventTarget { |
26 | | WEB_PLATFORM_OBJECT(Animation, DOM::EventTarget); |
27 | | JS_DECLARE_ALLOCATOR(Animation); |
28 | | |
29 | | public: |
30 | | static JS::NonnullGCPtr<Animation> create(JS::Realm&, JS::GCPtr<AnimationEffect>, Optional<JS::GCPtr<AnimationTimeline>>); |
31 | | static WebIDL::ExceptionOr<JS::NonnullGCPtr<Animation>> construct_impl(JS::Realm&, JS::GCPtr<AnimationEffect>, Optional<JS::GCPtr<AnimationTimeline>>); |
32 | | |
33 | 0 | FlyString const& id() const { return m_id; } |
34 | 0 | void set_id(FlyString value) { m_id = move(value); } |
35 | | |
36 | 0 | JS::GCPtr<AnimationEffect> effect() const { return m_effect; } |
37 | | void set_effect(JS::GCPtr<AnimationEffect>); |
38 | | |
39 | 0 | JS::GCPtr<AnimationTimeline> timeline() const { return m_timeline; } |
40 | | void set_timeline(JS::GCPtr<AnimationTimeline>); |
41 | | |
42 | 0 | Optional<double> const& start_time() const { return m_start_time; } |
43 | | void set_start_time(Optional<double> const&); |
44 | | |
45 | | Optional<double> current_time() const; |
46 | | WebIDL::ExceptionOr<void> set_current_time(Optional<double> const&); |
47 | | |
48 | 0 | double playback_rate() const { return m_playback_rate; } |
49 | | WebIDL::ExceptionOr<void> set_playback_rate(double value); |
50 | | |
51 | | Bindings::AnimationPlayState play_state() const; |
52 | | |
53 | | bool is_relevant() const; |
54 | | |
55 | | bool is_replaceable() const; |
56 | 0 | Bindings::AnimationReplaceState replace_state() const { return m_replace_state; } |
57 | | void set_replace_state(Bindings::AnimationReplaceState value); |
58 | | |
59 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-pending |
60 | 0 | bool pending() const { return m_pending_play_task == TaskState::Scheduled || m_pending_pause_task == TaskState::Scheduled; } |
61 | | |
62 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-ready |
63 | 0 | JS::NonnullGCPtr<JS::Object> ready() const { return *current_ready_promise()->promise(); } |
64 | | |
65 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-finished |
66 | 0 | JS::NonnullGCPtr<JS::Object> finished() const { return *current_finished_promise()->promise(); } |
67 | 0 | bool is_finished() const { return m_is_finished; } |
68 | | |
69 | | JS::GCPtr<WebIDL::CallbackType> onfinish(); |
70 | | void set_onfinish(JS::GCPtr<WebIDL::CallbackType>); |
71 | | JS::GCPtr<WebIDL::CallbackType> oncancel(); |
72 | | void set_oncancel(JS::GCPtr<WebIDL::CallbackType>); |
73 | | JS::GCPtr<WebIDL::CallbackType> onremove(); |
74 | | void set_onremove(JS::GCPtr<WebIDL::CallbackType>); |
75 | | |
76 | | enum class AutoRewind { |
77 | | Yes, |
78 | | No, |
79 | | }; |
80 | | enum class ShouldInvalidate { |
81 | | Yes, |
82 | | No, |
83 | | }; |
84 | | void cancel(ShouldInvalidate = ShouldInvalidate::Yes); |
85 | | WebIDL::ExceptionOr<void> finish(); |
86 | | WebIDL::ExceptionOr<void> play(); |
87 | | WebIDL::ExceptionOr<void> play_an_animation(AutoRewind); |
88 | | WebIDL::ExceptionOr<void> pause(); |
89 | | WebIDL::ExceptionOr<void> update_playback_rate(double); |
90 | | WebIDL::ExceptionOr<void> reverse(); |
91 | | void persist(); |
92 | | |
93 | | Optional<double> convert_an_animation_time_to_timeline_time(Optional<double>) const; |
94 | | Optional<double> convert_a_timeline_time_to_an_origin_relative_time(Optional<double>) const; |
95 | | |
96 | | JS::GCPtr<DOM::Document> document_for_timing() const; |
97 | | void notify_timeline_time_did_change(); |
98 | | |
99 | | void effect_timing_changed(Badge<AnimationEffect>); |
100 | | |
101 | 0 | virtual bool is_css_animation() const { return false; } |
102 | 0 | virtual bool is_css_transition() const { return false; } |
103 | | |
104 | 0 | JS::GCPtr<DOM::Element> owning_element() const { return m_owning_element; } |
105 | 0 | void set_owning_element(JS::GCPtr<DOM::Element> value) { m_owning_element = value; } |
106 | | |
107 | 0 | virtual AnimationClass animation_class() const { return AnimationClass::None; } |
108 | 0 | virtual Optional<int> class_specific_composite_order(JS::NonnullGCPtr<Animation>) const { return {}; } |
109 | | |
110 | 0 | unsigned int global_animation_list_order() const { return m_global_animation_list_order; } |
111 | | |
112 | 0 | auto release_saved_cancel_time() { return move(m_saved_cancel_time); } |
113 | | |
114 | | protected: |
115 | | Animation(JS::Realm&); |
116 | | |
117 | | virtual void initialize(JS::Realm&) override; |
118 | | virtual void visit_edges(Cell::Visitor&) override; |
119 | | |
120 | | private: |
121 | | enum class TaskState { |
122 | | None, |
123 | | Scheduled, |
124 | | }; |
125 | | |
126 | | enum class DidSeek { |
127 | | Yes, |
128 | | No, |
129 | | }; |
130 | | |
131 | | enum class SynchronouslyNotify { |
132 | | Yes, |
133 | | No, |
134 | | }; |
135 | | |
136 | | double associated_effect_end() const; |
137 | | double effective_playback_rate() const; |
138 | | |
139 | | void apply_any_pending_playback_rate(); |
140 | | WebIDL::ExceptionOr<void> silently_set_current_time(Optional<double>); |
141 | | void update_finished_state(DidSeek, SynchronouslyNotify); |
142 | | void reset_an_animations_pending_tasks(); |
143 | | |
144 | | void run_pending_play_task(); |
145 | | void run_pending_pause_task(); |
146 | | |
147 | | JS::NonnullGCPtr<WebIDL::Promise> current_ready_promise() const; |
148 | | JS::NonnullGCPtr<WebIDL::Promise> current_finished_promise() const; |
149 | | |
150 | | void invalidate_effect(); |
151 | | |
152 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-id |
153 | | FlyString m_id; |
154 | | |
155 | | // https://www.w3.org/TR/web-animations-1/#global-animation-list |
156 | | unsigned int m_global_animation_list_order { 0 }; |
157 | | |
158 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-effect |
159 | | JS::GCPtr<AnimationEffect> m_effect; |
160 | | |
161 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-timeline |
162 | | JS::GCPtr<AnimationTimeline> m_timeline; |
163 | | |
164 | | // https://www.w3.org/TR/web-animations-1/#animation-start-time |
165 | | Optional<double> m_start_time {}; |
166 | | |
167 | | // https://www.w3.org/TR/web-animations-1/#animation-hold-time |
168 | | Optional<double> m_hold_time {}; |
169 | | |
170 | | // https://www.w3.org/TR/web-animations-1/#previous-current-time |
171 | | Optional<double> m_previous_current_time {}; |
172 | | |
173 | | // https://www.w3.org/TR/web-animations-1/#playback-rate |
174 | | double m_playback_rate { 1.0 }; |
175 | | |
176 | | // https://www.w3.org/TR/web-animations-1/#pending-playback-rate |
177 | | Optional<double> m_pending_playback_rate {}; |
178 | | |
179 | | // https://www.w3.org/TR/web-animations-1/#dom-animation-replacestate |
180 | | Bindings::AnimationReplaceState m_replace_state { Bindings::AnimationReplaceState::Active }; |
181 | | |
182 | | // Note: The following promises are initialized lazily to avoid constructing them outside of an execution context |
183 | | // https://www.w3.org/TR/web-animations-1/#current-ready-promise |
184 | | mutable JS::GCPtr<WebIDL::Promise> m_current_ready_promise; |
185 | | |
186 | | // https://www.w3.org/TR/web-animations-1/#current-finished-promise |
187 | | mutable JS::GCPtr<WebIDL::Promise> m_current_finished_promise; |
188 | | bool m_is_finished { false }; |
189 | | |
190 | | // https://www.w3.org/TR/web-animations-1/#pending-play-task |
191 | | TaskState m_pending_play_task { TaskState::None }; |
192 | | |
193 | | // https://www.w3.org/TR/web-animations-1/#pending-pause-task |
194 | | TaskState m_pending_pause_task { TaskState::None }; |
195 | | |
196 | | // https://www.w3.org/TR/css-animations-2/#owning-element-section |
197 | | JS::GCPtr<DOM::Element> m_owning_element; |
198 | | |
199 | | Optional<HTML::TaskID> m_pending_finish_microtask_id; |
200 | | |
201 | | Optional<double> m_saved_play_time; |
202 | | Optional<double> m_saved_pause_time; |
203 | | Optional<double> m_saved_cancel_time; |
204 | | }; |
205 | | |
206 | | } |