Skip to content

Fix CSS step timing functions jump-both, jump-start, start at timestamp 0; Early return when possible#342

Open
yezhizhen wants to merge 30 commits intomainfrom
fix-quantization
Open

Fix CSS step timing functions jump-both, jump-start, start at timestamp 0; Early return when possible#342
yezhizhen wants to merge 30 commits intomainfrom
fix-quantization

Conversation

@yezhizhen
Copy link
Copy Markdown
Member

@yezhizhen yezhizhen commented Mar 23, 2026

Make early return happen earlier:

  1. total_progress ≥ 1.0 and total_progress < 0 now handled immediately. Also make it direction aware.
  2. At timestamp 0, for normal direction, non‑jump step functions (jump‑end, jump‑none, end) now return the start‑keyframe value early, instead of proceeding to interpolation.
  3. Zero-interval: we exit early to avoid division‑by‑zero in percentage_between_keyframes

Try
Servo PR: servo/servo#43594

delan and others added 26 commits March 5, 2026 04:42
Any ancestors of this commit are from upstream mozilla-central, with
some filtering and renaming. Our patches and sync tooling start here.

The sync tooling has all been squashed into this commit, based on:
https://github.com/servo/stylo/commits/64731e10dc8ef87ef52aa2fb9f988c3b2530f3a7
This is a rebase of ab75cec

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
- Bump stylo_* to 0.13.0
- Bump selectors to 0.36.0

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Required for firing events for contenteditable

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This way it can be shared among Gecko and Servo.

Stylo PR: servo/servo#43085
Upstream: https://bugzilla.mozilla.org/show_bug.cgi?id=2021743

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
…holder` and `::marker` (#322)

The `::placeholder` pseudo element should be a public pseudo element.
And, both `::placeholder` and `::marker` should have a property
restriction as defined in the spec. In stylo, the property restriction
has been computed in `PropertyFlags`.

Servo PR: servo/servo#43053

Signed-off-by: stevennovaryo <steven.novaryo@gmail.com>
This enables `jump-start`, `jump-end`, `jump-none`, `jump-both`.

The feature is left here 7 years ago. As tested with examples, all
`step-easing-function`s work as expected.

Servo PR: servo/servo#43061

---------

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>>
This change does two things:
1. Restores Servo's original theme colors from before support for system
color was added.
2. Eliminates the selection of default system color based on the color
scheme of the system. This code was dead code since
`is_dark_color_scheme` always returns `false`.

Servo PR: servo/servo#43107

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Adds a new file `style/device/mod.rs` with the `Device` logic that is
common for Gecko and Servo.

The Gecko-specific logic for `Device` is moved into
`style/device/gecko.rs`, and the previous `style/gecko/media_queries.rs`
is removed.

The Servo-specific logic for `Device` is moved into
`style/device/servo.rs`. The previous `servo/media_queries.rs` also had
logic for media features, which is now moved into
`style/servo/media_features.rs` for consistency with Gecko.

Servo PR: servo/servo#43146

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This makes Servo use the same `ListStyleType`  as Gecko. In particular:
- `counter()` and `counters()` will no longer accept a `none` style.
- `list-style-type` will now accept a string.
- Both `list-style-type` and counters will now accept arbitrary
keywords. However, Servo's flavor of Stylo will still reject
`@counter-style` rules. So non-predefined keywords will just default to
`decimal`.
- Both `list-style-type` and counters will now accept `symbols()`.

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This PR enables parsing `first-letter` pseudo element for servo. An
accompanying servo PR (servo/servo#43027) is
open

---------

Signed-off-by: Minghua Wu <michael.wu1107@gmail.com>
Signed-off-by: minghuaw <michael.wu1107@gmail.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Alex Touchet <26315797+atouchet@users.noreply.github.com>
This isn't really used. Nowadays we always test incremental layout as
it's considered an essential feature of the functioning of Servo.

After servo/servo#43207, Servo will no longer use this feature.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
The independently versioned crates in this workspace haven't had any
changes since the last release according to git.
#330 moved the module into
`style/device` but forgot to remove the old declaration. That's why
`cargo fmt` currently fails on `main`:
```
Error writing files: failed to resolve mod `media_queries`: /home/alaska/stylo/style/gecko/media_queries.rs does not exist
```

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
We currently never start animations that have no keyframes.
That is not correct. While such an animation will never change any
styles, it should still fire `animationstart` and `animationend` events
at the correct time, and that requires the animation to start first.

Servo companion PR: servo/servo#43454

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
@yezhizhen yezhizhen changed the title (To be updated) Fix quantization (To be updated) Fix step quantization Mar 23, 2026
.iter()
.rev()
.position(|step| total_progress as f32 <= 1. - step.start_percentage)
.position(|step| (total_progress as f32) <= 1. - step.start_percentage)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be < too?

Copy link
Copy Markdown
Member Author

@yezhizhen yezhizhen Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we shouldn't. Otherwise for reversed direction at timestamp 0, we jump while we shouldn't for jump-start, jump-both, start.

But there's more severe problem I've been trying to fix based on horrible test results..

// At progress 0, we need to handle step functions specially
// for "jump-both, jump-start, start" step functions.
if total_progress == 0.0 {
if let TimingFunction::Steps(_steps, pos) = &prev_keyframe.timing_function {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to think this seems the wrong place to handle timing functions. Shouldn't it be handled in calculate_step_output()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is handled there. But previously never get the chance to reach there (L723) due to early return.

The test result looks decent.

There's only one failure case that I hope to check tomorrow.

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed to Oriol. Btw I also find PropertyAnimation below weird, as it's resolving the timing function for every property. I imagine a better process would be:

  1. Use timing_function.calculate_output() to resolve the progress to a resolved_progress, which is number in [0, 1]
  2. Use prev_keyframe if resolved_progress is 0; Use next_keyframe is resolved_progress is 1
  3. Otherwise, calculate a linear interpolation between the two keyframes at resolved_progress

In this way we don't need handle special handling of any timing function here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be handled in calculate_step_output()?

fn calculate_step_output() is shared with Firefox. But Firefox doesn't have the problem we have here :/
This PR only changes Servo specific code.

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
@yezhizhen yezhizhen requested a review from xiaochengh March 24, 2026 05:16
@yezhizhen yezhizhen changed the title (To be updated) Fix step quantization Fix CSS step timing functions jump-both, jump-start, start at timestamp 0 Mar 24, 2026
@yezhizhen yezhizhen changed the title Fix CSS step timing functions jump-both, jump-start, start at timestamp 0 Fix CSS step timing functions jump-both, jump-start, start at timestamp 0 Mar 24, 2026
@yezhizhen yezhizhen changed the title Fix CSS step timing functions jump-both, jump-start, start at timestamp 0 Fix CSS step timing functions jump-both, jump-start, start at timestamp 0; Early return when possible Mar 24, 2026
Copy link
Copy Markdown
Member Author

@yezhizhen yezhizhen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also made early return happen earlier in various cases, instead of proceed to interpolation.

See "Make early return happen earlier" in description.

@yezhizhen yezhizhen marked this pull request as ready for review March 24, 2026 05:31
Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
Some(index) => &self.computed_steps[index],
None => return,
None => {
debug_assert!(false, "next_keyframe_index should always be Some");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because:

  1. We always have steps at 0% and 100%
    debug_assert!(intermediate_steps.first().unwrap().start_percentage == 0.);
    debug_assert!(intermediate_steps.last().unwrap().start_percentage == 1.);
  2. This always exists for total_progress in [0,1), no matter reverse or normal.

Comment on lines +691 to +692
// Detect zero interval. Prevent division by zero from percentage_between_keyframes.
if Some(prev_keyframe_index) == next_keyframe_index {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The % between two keyframes should never be zero, because if it was then IntermediateComputedKeyframe::generate_from_keyframes should have coalesced those two frames.

If you're seeing two keyframes with the same % then I suspect the bug is in that function and not here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not a bug of that function, but the result of index computation.

For example, reverse direction at total_progress == 0.0 can trigger the equality.

Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
@xiaochengh
Copy link
Copy Markdown
Contributor

Looks like there will be some extended discussion regarding the step functions.

Can we split the "early return when possible" part into a different PR and land it first?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.