Skip to content

Commit e56fd68

Browse files
committed
LibWeb: Expose AnimationEffect::{current,start}Time as CSSNumberish
1 parent bbafe08 commit e56fd68

File tree

7 files changed

+148
-31
lines changed

7 files changed

+148
-31
lines changed

Libraries/LibWeb/Animations/Animation.cpp

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <LibWeb/Bindings/AnimationPrototype.h>
1414
#include <LibWeb/Bindings/Intrinsics.h>
1515
#include <LibWeb/CSS/CSSAnimation.h>
16+
#include <LibWeb/CSS/CSSNumericValue.h>
1617
#include <LibWeb/DOM/Document.h>
1718
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
1819
#include <LibWeb/HTML/Window.h>
@@ -132,33 +133,84 @@ void Animation::set_timeline(GC::Ptr<AnimationTimeline> new_timeline)
132133
update_finished_state(DidSeek::No, SynchronouslyNotify::No);
133134
}
134135

136+
// https://drafts.csswg.org/web-animations-2/#validating-a-css-numberish-time
137+
WebIDL::ExceptionOr<Optional<TimeValue>> Animation::validate_a_css_numberish_time(Optional<CSS::CSSNumberish> const& time) const
138+
{
139+
// The procedure to validate a CSSNumberish time for an input value of time is based on the first condition that matches:
140+
141+
// FIXME: If all of the following conditions are true:
142+
// The animation is associated with a progress-based timeline, and
143+
// time is not a CSSNumeric value with percent units:
144+
// throw a TypeError.
145+
// return false;
146+
147+
// If all of the following conditions are true:
148+
if (
149+
// FIXME: The animation is not associated with a progress-based timeline, and
150+
151+
// time is a CSSNumericValue, and
152+
time.has_value() && time->has<GC::Root<CSS::CSSNumericValue>>() &&
153+
154+
// the units of time are not duration units:
155+
!time->get<GC::Root<CSS::CSSNumericValue>>()->type().matches_time({}) &&
156+
157+
// AD-HOC: While it's not mentioned in the spec WPT also expects us to support number CSSNumericValues here
158+
!time->get<GC::Root<CSS::CSSNumericValue>>()->type().matches_number({})) {
159+
// throw a TypeError.
160+
// return false.
161+
return WebIDL::SimpleException {
162+
WebIDL::SimpleExceptionType::TypeError,
163+
"CSSNumericValue must be a time for non-progress based animations"sv
164+
};
165+
}
166+
167+
// Otherwise
168+
// return true.
169+
170+
// AD-HOC: The spec doesn't say when we should absolutize the validated value so we do it here and return the
171+
// absolutized value instead of a boolean
172+
if (!time.has_value())
173+
return OptionalNone {};
174+
175+
// FIXME: Figure out which element we should use for this, for now we just use the document element of the current
176+
// window
177+
return TimeValue::from_css_numberish(time.value(), DOM::AbstractElement { *as<HTML::Window>(realm().global_object()).associated_document().document_element() });
178+
179+
VERIFY_NOT_REACHED();
180+
}
181+
135182
// https://www.w3.org/TR/web-animations-1/#dom-animation-starttime
136183
// https://www.w3.org/TR/web-animations-1/#set-the-start-time
137-
void Animation::set_start_time_for_bindings(Optional<double> const& new_start_time)
184+
WebIDL::ExceptionOr<void> Animation::set_start_time_for_bindings(Optional<CSS::CSSNumberish> const& new_start_time)
138185
{
139186
// Setting this attribute updates the start time using the procedure to set the start time of this object to the new
140187
// value.
141-
auto valid_start_time = new_start_time.map([](auto const& value) { return TimeValue { TimeValue::Type::Milliseconds, value }; });
142188

143-
// 1. Let timeline time be the current time value of the timeline that animation is associated with. If there is no
189+
// 1. Let valid start time be the result of running the validate a CSSNumberish time procedure with new start time as the input.
190+
// 2. If valid start time is false, abort this procedure.
191+
auto valid_start_time = TRY(validate_a_css_numberish_time(new_start_time));
192+
193+
// FIXME: 3. Set auto align start time to false.
194+
195+
// 4. Let timeline time be the current time value of the timeline that animation is associated with. If there is no
144196
// timeline associated with animation or the associated timeline is inactive, let the timeline time be
145197
// unresolved.
146198
auto timeline_time = m_timeline && !m_timeline->is_inactive() ? m_timeline->current_time() : Optional<TimeValue> {};
147199

148-
// 2. If timeline time is unresolved and new start time is resolved, make animation’s hold time unresolved.
200+
// 5. If timeline time is unresolved and new start time is resolved, make animation’s hold time unresolved.
149201
if (!timeline_time.has_value() && new_start_time.has_value())
150202
m_hold_time = {};
151203

152-
// 3. Let previous current time be animation’s current time.
204+
// 6. Let previous current time be animation’s current time.
153205
auto previous_current_time = current_time();
154206

155-
// 4. Apply any pending playback rate on animation.
207+
// 7. Apply any pending playback rate on animation.
156208
apply_any_pending_playback_rate();
157209

158-
// 5. Set animation’s start time to new start time.
210+
// 8. Set animation’s start time to new start time.
159211
m_start_time = valid_start_time;
160212

161-
// 6. Update animation’s hold time based on the first matching condition from the following,
213+
// 9. Update animation’s hold time based on the first matching condition from the following,
162214

163215
// -> If new start time is resolved,
164216
if (valid_start_time.has_value()) {
@@ -172,17 +224,19 @@ void Animation::set_start_time_for_bindings(Optional<double> const& new_start_ti
172224
m_hold_time = previous_current_time;
173225
}
174226

175-
// 7. If animation has a pending play task or a pending pause task, cancel that task and resolve animation’s current
227+
// 10. If animation has a pending play task or a pending pause task, cancel that task and resolve animation’s current
176228
// ready promise with animation.
177229
if (pending()) {
178230
m_pending_play_task = TaskState::None;
179231
m_pending_pause_task = TaskState::None;
180232
WebIDL::resolve_promise(realm(), current_ready_promise(), this);
181233
}
182234

183-
// 8. Run the procedure to update an animation’s finished state for animation with the did seek flag set to true,
235+
// 11. Run the procedure to update an animation’s finished state for animation with the did seek flag set to true,
184236
// and the synchronously notify flag set to false.
185237
update_finished_state(DidSeek::Yes, SynchronouslyNotify::No);
238+
239+
return {};
186240
}
187241

188242
// https://www.w3.org/TR/web-animations-1/#animation-current-time
@@ -213,9 +267,11 @@ Optional<TimeValue> Animation::current_time() const
213267
}
214268

215269
// https://www.w3.org/TR/web-animations-1/#animation-set-the-current-time
216-
WebIDL::ExceptionOr<void> Animation::set_current_time_for_bindings(Optional<double> const& seek_time)
270+
WebIDL::ExceptionOr<void> Animation::set_current_time_for_bindings(Optional<CSS::CSSNumberish> const& seek_time)
217271
{
218-
auto valid_seek_time = seek_time.map([](auto const& value) { return TimeValue { TimeValue::Type::Milliseconds, value }; });
272+
// AD-HOC: We validate here instead of within silently_set_current_time so we have access to the `TimeValue`
273+
// value within this function.
274+
auto valid_seek_time = TRY(validate_a_css_numberish_time(seek_time));
219275

220276
// 1. Run the steps to silently set the current time of animation to seek time.
221277
TRY(silently_set_current_time(valid_seek_time));
@@ -269,7 +325,7 @@ WebIDL::ExceptionOr<void> Animation::set_playback_rate(double new_playback_rate)
269325
// -> If animation is associated with a monotonically increasing timeline and the previous time is resolved,
270326
if (m_timeline && m_timeline->is_monotonically_increasing() && previous_time.has_value()) {
271327
// set the current time of animation to previous time.
272-
TRY(set_current_time_for_bindings(previous_time->as_milliseconds()));
328+
TRY(set_current_time_for_bindings(previous_time->as_css_numberish()));
273329
}
274330
// -> If animation is associated with a non-null timeline that is not monotonically increasing, the start time of
275331
// animation is resolved, associated effect end is not infinity, and either:
@@ -1002,10 +1058,10 @@ void Animation::apply_any_pending_playback_rate()
10021058
}
10031059

10041060
// https://www.w3.org/TR/web-animations-1/#animation-silently-set-the-current-time
1005-
WebIDL::ExceptionOr<void> Animation::silently_set_current_time(Optional<TimeValue> seek_time)
1061+
WebIDL::ExceptionOr<void> Animation::silently_set_current_time(Optional<TimeValue> valid_seek_time)
10061062
{
10071063
// 1. If seek time is an unresolved time value, then perform the following steps.
1008-
if (!seek_time.has_value()) {
1064+
if (!valid_seek_time.has_value()) {
10091065
// 1. If the current time is resolved, then throw a TypeError.
10101066
if (current_time().has_value()) {
10111067
return WebIDL::SimpleException {
@@ -1018,7 +1074,13 @@ WebIDL::ExceptionOr<void> Animation::silently_set_current_time(Optional<TimeValu
10181074
return {};
10191075
}
10201076

1021-
// 2. Update either animation’s hold time or start time as follows:
1077+
// 2. Let valid seek time be the result of running the validate a CSSNumberish time procedure with seek time as the input.
1078+
// 3. If valid seek time is false, abort this procedure.
1079+
// AD-HOC: We have already validated in the caller.
1080+
1081+
// FIXME: 4. Set auto align start time to false.
1082+
1083+
// 5. Update either animation’s hold time or start time as follows:
10221084

10231085
// -> If any of the following conditions are true:
10241086
// - animation’s hold time is resolved, or
@@ -1027,21 +1089,21 @@ WebIDL::ExceptionOr<void> Animation::silently_set_current_time(Optional<TimeValu
10271089
// - animation’s playback rate is 0,
10281090
if (m_hold_time.has_value() || !m_start_time.has_value() || !m_timeline || m_timeline->is_inactive() || m_playback_rate == 0.0) {
10291091
// Set animation’s hold time to seek time.
1030-
m_hold_time = seek_time;
1092+
m_hold_time = valid_seek_time;
10311093
}
10321094
// -> Otherwise,
10331095
else {
10341096
// Set animation’s start time to the result of evaluating timeline time - (seek time / playback rate) where
10351097
// timeline time is the current time value of timeline associated with animation.
1036-
m_start_time = m_timeline->current_time().value() - (seek_time.value() / m_playback_rate);
1098+
m_start_time = m_timeline->current_time().value() - (valid_seek_time.value() / m_playback_rate);
10371099
}
10381100

1039-
// 3. If animation has no associated timeline or the associated timeline is inactive, make animation’s start time
1101+
// 6. If animation has no associated timeline or the associated timeline is inactive, make animation’s start time
10401102
// unresolved.
10411103
if (!m_timeline || m_timeline->is_inactive())
10421104
m_start_time = {};
10431105

1044-
// 4. Make animation’s previous current time unresolved.
1106+
// 7. Make animation’s previous current time unresolved.
10451107
m_previous_current_time = {};
10461108

10471109
return {};

Libraries/LibWeb/Animations/Animation.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,20 @@ class Animation : public DOM::EventTarget {
4242
void set_timeline(GC::Ptr<AnimationTimeline>);
4343

4444
// https://drafts.csswg.org/web-animations-2/#dom-animation-starttime
45-
Optional<double> start_time_for_bindings() const
45+
NullableCSSNumberish start_time_for_bindings() const
4646
{
47-
return start_time().map([](auto const& start_time) { return start_time.as_milliseconds(); });
47+
return NullableCSSNumberish::from_optional_css_numberish_time(start_time());
4848
}
4949
Optional<TimeValue> start_time() const { return m_start_time; }
50-
void set_start_time_for_bindings(Optional<double> const&);
50+
WebIDL::ExceptionOr<void> set_start_time_for_bindings(Optional<CSS::CSSNumberish> const&);
5151

5252
// https://drafts.csswg.org/web-animations-2/#dom-animation-currenttime
53-
Optional<double> current_time_for_bindings() const
53+
NullableCSSNumberish current_time_for_bindings() const
5454
{
55-
return current_time().map([](auto const& current_time) { return current_time.as_milliseconds(); });
55+
return NullableCSSNumberish::from_optional_css_numberish_time(current_time());
5656
}
5757
Optional<TimeValue> current_time() const;
58-
WebIDL::ExceptionOr<void> set_current_time_for_bindings(Optional<double> const&);
58+
WebIDL::ExceptionOr<void> set_current_time_for_bindings(Optional<CSS::CSSNumberish> const&);
5959

6060
double playback_rate() const { return m_playback_rate; }
6161
WebIDL::ExceptionOr<void> set_playback_rate(double value);
@@ -155,6 +155,8 @@ class Animation : public DOM::EventTarget {
155155

156156
double effective_playback_rate() const;
157157

158+
WebIDL::ExceptionOr<Optional<TimeValue>> validate_a_css_numberish_time(Optional<CSS::CSSNumberish> const&) const;
159+
158160
void apply_any_pending_playback_rate();
159161
WebIDL::ExceptionOr<void> silently_set_current_time(Optional<TimeValue>);
160162
void update_finished_state(DidSeek, SynchronouslyNotify);

Libraries/LibWeb/Animations/Animation.idl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ interface Animation : EventTarget {
1111
attribute DOMString id;
1212
attribute AnimationEffect? effect;
1313
attribute AnimationTimeline? timeline;
14-
[ImplementedAs=start_time_for_bindings] attribute double? startTime;
15-
[ImplementedAs=current_time_for_bindings] attribute double? currentTime;
14+
[ImplementedAs=start_time_for_bindings] attribute CSSNumberish? startTime;
15+
[ImplementedAs=current_time_for_bindings] attribute CSSNumberish? currentTime;
1616
attribute double playbackRate;
1717
[ImplementedAs=play_state_for_bindings] readonly attribute AnimationPlayState playState;
1818
readonly attribute AnimationReplaceState replaceState;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright (c) 2025, Callum Law <[email protected]>.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include "TimeValue.h"
8+
#include <LibWeb/CSS/CSSUnitValue.h>
9+
#include <LibWeb/CSS/StyleValues/CalculatedStyleValue.h>
10+
11+
namespace Web::Animations {
12+
13+
TimeValue TimeValue::from_css_numberish(CSS::CSSNumberish const& time, DOM::AbstractElement const& abstract_element)
14+
{
15+
if (time.has<double>())
16+
return { Type::Milliseconds, time.get<double>() };
17+
18+
auto const& numeric_value = time.get<GC::Root<CSS::CSSNumericValue>>();
19+
20+
// NB: Skip creating a calculation node for simple unit values
21+
if (auto const* unit_value = as_if<CSS::CSSUnitValue>(*numeric_value)) {
22+
if (unit_value->type().matches_number({}))
23+
return { Type::Milliseconds, unit_value->value() };
24+
25+
if (unit_value->type().matches_time({}))
26+
return { Type::Milliseconds, MUST(unit_value->to("ms"_fly_string))->value() };
27+
28+
VERIFY_NOT_REACHED();
29+
}
30+
31+
auto const& calculation_node = MUST(numeric_value->create_calculation_node({}));
32+
33+
VERIFY(calculation_node->numeric_type().has_value());
34+
35+
auto style_value = CSS::CalculatedStyleValue::create(calculation_node, calculation_node->numeric_type().value(), {});
36+
37+
CSS::CalculationResolutionContext calculation_resolution_context {
38+
.length_resolution_context = CSS::Length::ResolutionContext::for_element(abstract_element),
39+
.abstract_element = abstract_element,
40+
};
41+
42+
if (style_value->resolves_to_number())
43+
return { Type::Milliseconds, style_value->resolve_number(calculation_resolution_context).value() };
44+
45+
if (style_value->resolves_to_time())
46+
return { Type::Milliseconds, style_value->resolve_time(calculation_resolution_context)->to_milliseconds() };
47+
48+
VERIFY_NOT_REACHED();
49+
}
50+
51+
}

Libraries/LibWeb/Animations/TimeValue.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Web::Animations {
1313

1414
struct TimeValue {
15+
static TimeValue from_css_numberish(CSS::CSSNumberish const&, DOM::AbstractElement const&);
1516
static TimeValue create_zero(GC::Ptr<AnimationTimeline> const& timeline)
1617
{
1718
// FIXME: Return 0% rather than 0ms for progress based timelines

Libraries/LibWeb/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ set(SOURCES
1818
Animations/AnimationEffect.cpp
1919
Animations/AnimationPlaybackEvent.cpp
2020
Animations/AnimationTimeline.cpp
21+
Animations/TimeValue.cpp
2122
Animations/DocumentTimeline.cpp
2223
Animations/KeyframeEffect.cpp
2324
Animations/PseudoElementParsing.cpp

Tests/LibWeb/Text/expected/wpt-import/web-animations/timing-model/animations/setting-the-start-time-of-an-animation.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ Harness status: OK
22

33
Found 13 tests
44

5-
8 Pass
6-
5 Fail
7-
Fail Validate different value types that can be used to set start time
5+
9 Pass
6+
4 Fail
7+
Pass Validate different value types that can be used to set start time
88
Fail Setting the start time of an animation without an active timeline
99
Fail Setting an unresolved start time an animation without an active timeline does not clear the current time
1010
Pass Setting the start time clears the hold time

0 commit comments

Comments
 (0)