diff --git a/docs/validation/css-animation-fill-mode-661-242.md b/docs/validation/css-animation-fill-mode-661-242.md index 88491e8b..e71930ac 100644 --- a/docs/validation/css-animation-fill-mode-661-242.md +++ b/docs/validation/css-animation-fill-mode-661-242.md @@ -39,9 +39,10 @@ clear the override through the established runtime lifecycle. - Existing keyframe-stop and compatible filter-list limits are unchanged. - No timer, queue, visual-tree node, selector pass, or per-frame allocation is added. Host timestamps and visibility/clip frame-demand gates are preserved. -- `animation-play-state`, zero-duration and zero-iteration fill, multiple - animations, translate/scale keyframes, pseudo-element animation, and the Web - Animations API remain deferred. +- Translate/scale keyframes, pseudo-element animation, animation + start/iteration/cancel events, and the Web Animations API remain deferred. + Play state, multiple lists, and zero-time fill are covered by #665, #672, + and #674. - Unchanged Code OSS uses `both` for delayed Quick Input, action-widget, and Chat entry animations. This slice prevents their supported opacity before-phase flash. Their translate/scale keyframes remain outside the rotate-only bound. diff --git a/docs/validation/css-animation-multiple-lists-672-242.md b/docs/validation/css-animation-multiple-lists-672-242.md index 7bad494a..cc52b441 100644 --- a/docs/validation/css-animation-multiple-lists-672-242.md +++ b/docs/validation/css-animation-multiple-lists-672-242.md @@ -48,8 +48,9 @@ detach, and frame demand. The dedicated browser contract provides a WPT-aligned observable reduction for coordination, ordering, serialization, and cleanup. Additive/accumulate composition, `animation-composition`, Web Animations API -objects, more than eight live tracks, zero-duration event expansion, additional -animated properties, transition/animation compositing changes, and ABI or -AppScene changes remain deferred. Under the fast implementation policy, only +objects, more than eight live tracks, animation start/iteration/cancel event +expansion, additional animated properties, transition/animation compositing +changes, and ABI or AppScene changes remain deferred. Zero-duration/count end +semantics are covered by #674. Under the fast implementation policy, only `git diff --check` is executed. Native, browser, package, and unchanged Code OSS acceptance remain unexecuted. diff --git a/docs/validation/css-animation-play-state-665-242.md b/docs/validation/css-animation-play-state-665-242.md index 7e373159..e737b730 100644 --- a/docs/validation/css-animation-play-state-665-242.md +++ b/docs/validation/css-animation-play-state-665-242.md @@ -35,11 +35,11 @@ node-owned hold with the established animation runtime lifecycle. - Resume uses the existing visibility and paint-area gates. Opacity, rotation, and filters remain paint-only and do not add selector, cascade, layout, scene node, queue, or per-frame allocation work. -- The runtime still configures one animation and the first coordinated-list item. +- The runtime now configures up to eight coordinated tracks through #672. Existing duration, iteration, keyframe-stop, and filter-list bounds remain. -- Zero-duration/zero-count behavior, multiple animations, translate/scale, - pseudo-elements, additional animation events, and Web Animations playback APIs - remain deferred. +- Translate/scale, pseudo-elements, animation start/iteration/cancel events, and + Web Animations playback APIs remain deferred. Zero-duration/count end + semantics are covered by #674. ## Evidence diff --git a/docs/validation/css-animation-zero-time-674-242.md b/docs/validation/css-animation-zero-time-674-242.md new file mode 100644 index 00000000..3a8a167a --- /dev/null +++ b/docs/validation/css-animation-zero-time-674-242.md @@ -0,0 +1,63 @@ +# Zero-time CSS animation source validation + +Issue #674 completes the zero-duration and zero-iteration-count boundary for +WebScene's retained multi-track CSS animation runtime under #242. Both values are +valid CSS. Either makes active duration zero, including `0s × infinite`, while +the animation still has before and after phases and can dispatch completion. +The eight-track cap and coordinated-list rules from #672 remain unchanged. + +## Phase and sampling contract + +A positive delay retains the before phase. Backwards or both fill samples the +directed start of the first iteration; none or forwards contributes nothing. +Once the delay boundary is reached, the track settles immediately in its after +phase. A zero or negative delay reaches that phase in the same bounded host +update. + +For zero iteration count, forwards or both fill also samples the directed start +of the first iteration because no iteration ran. For zero duration with a +positive finite or fractional count, forwards fill uses the terminal progress +for that count and direction. A zero-duration infinite count has zero active +duration; its terminal simple progress follows the Web Animations infinity rule, +with reverse mapped backwards and infinite alternate directions treated as +forwards. Timing is applied only when the resolved progress falls between +retained keyframe stops. + +A paused positive delay remains held in the before phase and requests no frame. +Resuming preserves the unconsumed delay, then performs one instantaneous +settlement. A paused zero-time track whose delay boundary is already reached has +no active interval to hold and settles once. Filled results remain retained +without frame demand. + +## Events, lifecycle, and bounds + +- Every resolved supported track appends at most one `animationend`, with + `elapsedTime` equal to the zero active duration. +- Tracks completing in the same host update append records in animation-list + order. Fill or play-state mutations do not duplicate an already emitted end. +- Identity mutation rearms only that track. Removal, detach, navigation, and + teardown discard retained paint and queued stale records. +- Initial configuration and a zero-time CSSOM phase change invalidate the + duplicate-timestamp guard once, allowing settlement in the current host + update. Completed, filled, or paused tracks do not sustain frame demand. +- Sampling remains an eight-track retained scan. No selector matching, cascade, + layout, timer spin, or visual-tree reconstruction is added to the frame path. + +The phase and progress rules align with CSS Animations Level 1 and the Web +Animations active-duration, overall-progress, simple-progress, and direction +algorithms. + +## Evidence and deferred scope + +`test_zero_time_animation_tracks_settle_without_frame_demand` covers zero +values in shorthand and longhand CSSOM, positive and fractional counts, zero +count, normal/reverse/alternate mapping, all relevant fill phases, a positive +delay, pause/resume, same-update mutation, ordered exactly-once completion, +removal, detach, and demand settlement. The dedicated browser contract provides +the corresponding WPT-aligned observable reduction. + +Animation start/iteration/cancel event expansion, additive composition, Web +Animations API objects, more than eight live tracks, additional animated +properties, pseudo-elements, and ABI or AppScene changes remain deferred. Under +the fast implementation policy, only `git diff --check` is executed. Native, +browser, package, and unchanged Code OSS acceptance remain unexecuted. diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_transitions.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_transitions.h index 708268f1..a48f3def 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_transitions.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_transitions.h @@ -427,11 +427,11 @@ inline void configure_keyframes(node_style& style, track.rotation_keyframes = definition->second.rotation_stops; track.filter_keyframes = definition->second.filter_stops; } - const auto runnable = track.duration_ms > 0 && track.iterations != 0 - && (track.opacity_keyframes.size() >= 2U + const auto has_supported_effect = + track.opacity_keyframes.size() >= 2U || track.rotation_keyframes.size() >= 2U - || track.filter_keyframes.size() >= 2U); - if (runnable) { + || track.filter_keyframes.size() >= 2U; + if (has_supported_effect) { std::ostringstream signature; signature << normalized_name << '|' << track.duration_ms << '|' << track.delay_ms << '|' << track.iterations << '|' diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_native_dom_animations.inc b/experiments/WebScene.NativeEngine.Probe/native/webscene_native_dom_animations.inc index caae6aa5..77624042 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_native_dom_animations.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_native_dom_animations.inc @@ -30,6 +30,18 @@ float terminal_keyframe_progress( node_style::animation_data::direction_kind direction) noexcept { const auto count = std::max(0.0, static_cast(iterations)); + if (count == 0.0) { + // A zero-count effect never enters its first iteration. Both backwards + // and forwards fill therefore use the start of that first iteration. + return directed_keyframe_progress(0.0F, 0.0, direction); + } + if (!std::isfinite(count)) { + // Zero-duration infinite effects have zero active duration. Web + // Animations resolves their after-phase simple progress to one; an + // infinite alternate iteration index is treated as forwards. + return direction == node_style::animation_data::direction_kind::reverse + ? 0.0F : 1.0F; + } const auto whole = std::floor(count); const auto fractional = count - whole; const auto progress = fractional > 0.0 @@ -38,6 +50,16 @@ float terminal_keyframe_progress( return directed_keyframe_progress(progress, iteration, direction); } +float keyframe_active_duration_ms( + const node_style::animation_data::keyframe_animation& animation) noexcept +{ + if (animation.duration_ms <= 0 || animation.iterations <= 0) return 0; + if (!std::isfinite(animation.iterations)) { + return std::numeric_limits::infinity(); + } + return animation.duration_ms * animation.iterations; +} + bool fills_before(node_style::animation_data::fill_kind fill) noexcept { return fill == node_style::animation_data::fill_kind::backwards @@ -251,11 +273,29 @@ void native_document::update_style_animations(dom_node& node) const auto& configured_keyframes = node.style.animations().keyframe_animations; auto keyframe_set_changed = animation.keyframe_animations.size() != configured_keyframes.size(); + auto keyframe_phase_changed = keyframe_set_changed; for (size_t index = 0U; !keyframe_set_changed && index < configured_keyframes.size(); ++index) { keyframe_set_changed = animation.keyframe_animations[index].signature != configured_keyframes[index].signature; } + for (size_t index = 0U; + !keyframe_phase_changed && index < configured_keyframes.size(); ++index) { + const auto desired_pause = !configured_keyframes[index].signature.empty() + && configured_keyframes[index].play_state + == node_style::animation_data::play_kind::paused; + keyframe_phase_changed = + animation.keyframe_animations[index].fill_mode + != configured_keyframes[index].fill_mode + || animation.keyframe_animations[index].paused != desired_pause; + } + if (keyframe_phase_changed) { + // Permit the host to settle a zero-length phase change again at the + // current timestamp; the ordinary duplicate-frame guard still applies + // once the updated state has been sampled. + last_animation_advance_timestamp_ms_ = + std::numeric_limits::quiet_NaN(); + } if (keyframe_set_changed && animation_events_) { std::erase_if(*animation_events_, [&](const auto& event) { return event.node_id == node.id; @@ -1083,18 +1123,19 @@ bool native_document::advance_animations() noexcept 0.0F, 0.0, configured.direction); } } else { - const auto duration = std::max(0.001F, configured.duration_ms); - const auto finite = std::isfinite(configured.iterations); - const auto total_duration = finite - ? duration * std::max(0.0F, configured.iterations) - : 0.0F; - if (finite && elapsed_ms >= total_duration) { + const auto active_duration = + keyframe_active_duration_ms(configured); + const auto instantaneous = active_duration == 0; + if (instantaneous || elapsed_ms >= active_duration) { contributes = fills_after(configured.fill_mode); if (contributes) { progress = terminal_keyframe_progress( configured.iterations, configured.direction); } - if (!runtime.paused) { + // A paused positive delay remains in its before phase. + // Once the zero-length active interval is reached, + // however, there is no interval left to hold. + if (!runtime.paused || instantaneous) { runtime.active = false; runtime.filled = contributes; completed = true; @@ -1103,7 +1144,8 @@ bool native_document::advance_animations() noexcept contributes = true; runtime.filled = false; progress = active_keyframe_progress( - elapsed_ms, duration, configured.direction); + elapsed_ms, configured.duration_ms, + configured.direction); } } } @@ -1210,7 +1252,7 @@ bool native_document::advance_animations() noexcept node.id, "animationend", runtime.name, - configured.duration_ms * configured.iterations / 1000.0F}); + keyframe_active_duration_ms(configured) / 1000.0F}); runtime.end_event_sent = true; } } diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_animation_cssom_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_animation_cssom_tests.inc index c0f73664..8d413feb 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_animation_cssom_tests.inc +++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_animation_cssom_tests.inc @@ -1062,6 +1062,140 @@ void test_multiple_animation_lists_coordinate_bounded_tracks( engine, false, "detached animation lists retained host-frame demand"); } +void test_zero_time_animation_tracks_settle_without_frame_demand( + webscene_engine* engine) +{ + animation_frame_and_wait(engine, 11000.0, 7130U); + execute_and_wait(engine, R"JS( + (() => { + document.body.innerHTML = ` + +
+
+
+
+
`; + globalThis.__zeroEnds = []; + for (const node of document.querySelectorAll('div')) { + node.addEventListener('animationend', event => __zeroEnds.push({ + id: node.id, name: event.animationName, elapsed: event.elapsedTime + })); + } + void document.body.offsetHeight; + })() + )JS", "animation-zero-setup.js"); + + animation_frame_and_wait(engine, 11250.0, 7131U); + const auto before = evaluate(engine, R"JS( + (() => { + const opacity = id => Number(getComputedStyle( + document.getElementById(id)).opacity); + const zeroCountStyle = getComputedStyle( + document.getElementById('zero-count')); + const matrix = zeroCountStyle.transform.slice(7, -1).split(',').map(Number); + const angle = Math.abs(Math.atan2(matrix[1], matrix[0]) * 180 / Math.PI); + const ordered = __zeroEnds.filter(event => event.id === 'ordered'); + return Math.abs(opacity('delayed') - 1) < .001 + && Math.abs(opacity('zero-count') - 1) < .001 + && Math.abs(opacity('fraction') - .5) < .001 + && Math.abs(opacity('unfilled') - .3) < .001 + && Math.abs(opacity('negative')) < .001 + && Math.abs(opacity('paused-now') - 1) < .001 + && Math.abs(opacity('paused-delay') - 0) < .001 + && Math.abs(angle - 180) < .001 + && zeroCountStyle.filter === 'brightness(1.5)' + && ordered.length === 2 + && ordered[0].name === 'zero-fade' + && ordered[1].name === 'zero-fade' + && __zeroEnds.every(event => event.elapsed === 0) + && getComputedStyle(document.getElementById('zero-count')) + .animationIterationCount === '0' + && getComputedStyle(document.getElementById('fraction')) + .animation.includes('0s'); + })() + )JS", "animation-zero-before.js"); + require(before == "true", + "zero duration/count did not settle directed fill and ordered completion"); + wait_for_animation_frame_demand( + engine, true, "running zero-duration delay lost host-frame demand"); + + animation_frame_and_wait(engine, 11500.0, 7132U); + const auto delayed_complete = evaluate(engine, R"JS( + (() => { + const delayed = document.getElementById('delayed'); + return Math.abs(Number(getComputedStyle(delayed).opacity)) < .001 + && __zeroEnds.filter(event => event.id === 'delayed').length === 1 + && __zeroEnds.filter(event => event.id === 'paused-delay').length === 0; + })() + )JS", "animation-zero-delay-complete.js"); + require(delayed_complete == "true", + "zero-duration positive delay missed its instantaneous terminal phase"); + wait_for_animation_frame_demand( + engine, false, "settled or paused zero-time tracks retained frame demand"); + + execute_and_wait(engine, R"JS( + (() => { + const paused = document.getElementById('paused-delay'); + paused.style.animationPlayState = 'running'; + return paused.style.animationPlayState; + })() + )JS", "animation-zero-resume-delay.js"); + animation_frame_and_wait(engine, 11750.0, 7133U); + wait_for_animation_frame_demand( + engine, true, "resumed positive delay did not request its bounded frame"); + animation_frame_and_wait(engine, 12000.0, 7134U); + const auto resumed = evaluate(engine, R"JS( + (() => { + const paused = document.getElementById('paused-delay'); + return Math.abs(Number(getComputedStyle(paused).opacity) - 1) < .001 + && __zeroEnds.filter(event => event.id === 'paused-delay').length === 1; + })() + )JS", "animation-zero-resumed-result.js"); + require(resumed == "true", + "resumed zero-duration delay did not settle exactly once"); + wait_for_animation_frame_demand( + engine, false, "completed resumed zero-time track retained frame demand"); + + execute_and_wait(engine, R"JS( + (() => { + const node = document.getElementById('fraction'); + node.style.animationIterationCount = '0'; + document.getElementById('ordered').style.animation = 'none'; + document.getElementById('zero-count').remove(); + })() + )JS", "animation-zero-mutate-remove.js"); + animation_frame_and_wait(engine, 12000.0, 7135U); + const auto mutation = evaluate(engine, R"JS( + (() => Math.abs(Number(getComputedStyle( + document.getElementById('fraction')).opacity)) < .001 + && getComputedStyle(document.getElementById('ordered')).opacity === '1')() + )JS", "animation-zero-mutate-result.js"); + require(mutation == "true", + "same-timestamp zero-count mutation or removal left stale retained paint"); + + execute_and_wait(engine, + "document.body.innerHTML = '
zero-time animations detached
'", + "animation-zero-detach.js"); + wait_for_animation_frame_demand( + engine, false, "detached zero-time animations retained frame demand"); +} + void test_filter_keyframes_use_host_clock_and_retained_paint( webscene_engine* engine) { diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp index 603ff70d..6479c542 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp +++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp @@ -1141,6 +1141,7 @@ int main() test_animation_fill_maps_before_after_and_preserves_timeline(focused_engine); test_animation_play_state_holds_and_resumes_host_time(focused_engine); test_multiple_animation_lists_coordinate_bounded_tracks(focused_engine); + test_zero_time_animation_tracks_settle_without_frame_demand(focused_engine); webscene_engine_destroy(focused_engine); return 0; } @@ -1639,6 +1640,7 @@ int main() test_animation_fill_maps_before_after_and_preserves_timeline(engine); test_animation_play_state_holds_and_resumes_host_time(engine); test_multiple_animation_lists_coordinate_bounded_tracks(engine); + test_zero_time_animation_tracks_settle_without_frame_demand(engine); // This regression uses fixed host timestamps beginning at 2700 ms. Give it // a fresh timeline so earlier animation tests cannot make those frames stale. webscene_engine_destroy(engine); diff --git a/tests/WebPlatformSubset/capabilities.json b/tests/WebPlatformSubset/capabilities.json index 24282bf1..6557d358 100644 --- a/tests/WebPlatformSubset/capabilities.json +++ b/tests/WebPlatformSubset/capabilities.json @@ -97,11 +97,11 @@ }, { "family": "transitions-and-animation", - "required": ["host-frame-driven requestAnimationFrame", "transition longhands and delay", "four-number cubic-bezier timing", "translate, scale, rotate, opacity, foreground color, background color, supported compound filters, left and top interpolation", "background shorthand transition aliasing", "filter none identity interpolation", "logical inset transition aliasing", "intermediate computed style, paint and client geometry", "transition run/start/cancel/end events", "cancellation and reversal", "bounded paint samples", "dense bounded rotation cadence", "native opacity, rotate, and bounded filter keyframes", "native animation shorthand and longhands", "native staggered delay and infinite iteration", "single-animation normal, reverse, alternate, and alternate-reverse direction", "single-animation none, forwards, backwards, and both fill", "single-animation running and paused play state", "bounded comma-separated animation lists"], + "required": ["host-frame-driven requestAnimationFrame", "transition longhands and delay", "four-number cubic-bezier timing", "translate, scale, rotate, opacity, foreground color, background color, supported compound filters, left and top interpolation", "background shorthand transition aliasing", "filter none identity interpolation", "logical inset transition aliasing", "intermediate computed style, paint and client geometry", "transition run/start/cancel/end events", "cancellation and reversal", "bounded paint samples", "dense bounded rotation cadence", "native opacity, rotate, and bounded filter keyframes", "native animation shorthand and longhands", "native staggered delay and infinite iteration", "single-animation normal, reverse, alternate, and alternate-reverse direction", "single-animation none, forwards, backwards, and both fill", "single-animation running and paused play state", "bounded comma-separated animation lists", "zero-duration and zero-iteration-count animation phases"], "candidate": ["innerHTML transition-longhand retention", "inline transition recascade and per-longhand importance", "computed transition times serialized in seconds", "synthetic click timer task ordering"], "productEvidence": ["calendar segmented-tab underline", "Code OSS Chat and session filter transitions", "Code OSS dictation brightness and incremental Chat blur keyframes", "automatically discovered three-dot loader animation", "automatically discovered rotating spinner", "animated toolbar and dialog state changes", "continuous live-resize chart layout"], - "coverage": ["contracts/segmented-transform-transition.html", "contracts/transform-none-transition.html", "contracts/paint-transitions-temporal.html", "contracts/css-filter-transition-temporal.html", "contracts/css-filter-transition-midpoint.html", "contracts/logical-inset-transition-cadence.html", "contracts/rotation-transition-cadence.html", "contracts/dynamic-transition-style-task-order.html", "contracts/css-opacity-keyframes-temporal.html", "contracts/css-animation-fill-mode-forwards.html", "contracts/css-animation-fill-mode-boundaries.html", "contracts/css-animation-direction.html", "contracts/css-animation-play-state.html", "contracts/css-animation-multiple-lists.html", "contracts/css-rotation-keyframes-cadence.html", "contracts/css-filter-keyframes-temporal.html", "contracts/css-filter-keyframes-midpoint.html", "CssTransformTransitionTests", "test_inline_transition_longhands_survive_dynamic_parse_and_recascade", "test_logical_inset_transition_smooths_throttled_pointer_updates", "test_filter_keyframes_use_host_clock_and_retained_paint", "test_animation_direction_maps_cycles_and_terminal_boundaries", "test_animation_fill_maps_before_after_and_preserves_timeline", "test_animation_play_state_holds_and_resumes_host_time", "test_multiple_animation_lists_coordinate_bounded_tracks", "native host-clock transition, retained filter paint, and keyframe regressions", "hosted component automatically discovered temporal checkpoints", "hosted component intermediate viewport-publication gate"], - "gap": "Native is required to pass the bounded transform/opacity/foreground-color/background-color/filter transition slice, including events, cancellation, reversal, authored compound order, identity interpolation, paint, and cadence. Filter transitions and keyframes are limited to the retained blur, brightness, contrast, grayscale, and saturate functions and their existing list/value bounds. Bounded filter keyframes cover Code OSS dictation brightness and incremental Chat blur, including compatible compound order, none identities, finite fill, animationend, retained paint, and host-clock cleanup. The single-animation keyframe slice maps normal, reverse, alternate, and alternate-reverse through negative delays and finite, fractional, or infinite iteration counts before opacity, rotation, and bounded-filter sampling. For positive-duration, nonzero-count animations, none, forwards, backwards, and both apply direction-aware fill boundaries to those supported properties without extending frame demand; positive-delay backwards fill samples its boundary without applying the between-keyframe timing function. Running and paused play state now hold and resume the same bounded host-clock timeline, including delay, direction, fill, supported-property paint, exactly-once completion, and zero paused frame demand. Up to eight comma-separated animations coordinate repeated longhand lists with independent clocks, fill, pause, ordered completion, and last-contributing replace composition for the supported properties. Identity interpolation from transform:none is part of the neutral forward/reverse contract. The candidate dynamic-style reduction passes Chrome and native and closes Bootstrap Modal's static-backdrop callback guard by retaining inline transition longhands through parser construction and recascade, honoring per-longhand importance, and exposing computed times in seconds. Native also passes candidate opacity and rotate() CSS Animations contracts, while generic discovery proves changing staggered-dot and spinner paint samples. Other animated properties, zero-duration or zero-count fill, additive animation composition, more than eight live tracks per element, and the complete Web Animations API are not claimed." + "coverage": ["contracts/segmented-transform-transition.html", "contracts/transform-none-transition.html", "contracts/paint-transitions-temporal.html", "contracts/css-filter-transition-temporal.html", "contracts/css-filter-transition-midpoint.html", "contracts/logical-inset-transition-cadence.html", "contracts/rotation-transition-cadence.html", "contracts/dynamic-transition-style-task-order.html", "contracts/css-opacity-keyframes-temporal.html", "contracts/css-animation-fill-mode-forwards.html", "contracts/css-animation-fill-mode-boundaries.html", "contracts/css-animation-direction.html", "contracts/css-animation-play-state.html", "contracts/css-animation-multiple-lists.html", "contracts/css-animation-zero-time.html", "contracts/css-rotation-keyframes-cadence.html", "contracts/css-filter-keyframes-temporal.html", "contracts/css-filter-keyframes-midpoint.html", "CssTransformTransitionTests", "test_inline_transition_longhands_survive_dynamic_parse_and_recascade", "test_logical_inset_transition_smooths_throttled_pointer_updates", "test_filter_keyframes_use_host_clock_and_retained_paint", "test_animation_direction_maps_cycles_and_terminal_boundaries", "test_animation_fill_maps_before_after_and_preserves_timeline", "test_animation_play_state_holds_and_resumes_host_time", "test_multiple_animation_lists_coordinate_bounded_tracks", "test_zero_time_animation_tracks_settle_without_frame_demand", "native host-clock transition, retained filter paint, and keyframe regressions", "hosted component automatically discovered temporal checkpoints", "hosted component intermediate viewport-publication gate"], + "gap": "Native is required to pass the bounded transform/opacity/foreground-color/background-color/filter transition slice, including events, cancellation, reversal, authored compound order, identity interpolation, paint, and cadence. Filter transitions and keyframes are limited to the retained blur, brightness, contrast, grayscale, and saturate functions and their existing list/value bounds. Bounded filter keyframes cover Code OSS dictation brightness and incremental Chat blur, including compatible compound order, none identities, finite fill, animationend, retained paint, and host-clock cleanup. The single-animation keyframe slice maps normal, reverse, alternate, and alternate-reverse through negative delays and finite, fractional, or infinite iteration counts before opacity, rotation, and bounded-filter sampling. For positive-duration, nonzero-count animations, none, forwards, backwards, and both apply direction-aware fill boundaries to those supported properties without extending frame demand; positive-delay backwards fill samples its boundary without applying the between-keyframe timing function. Running and paused play state now hold and resume the same bounded host-clock timeline, including delay, direction, fill, supported-property paint, exactly-once completion, and zero paused frame demand. Up to eight comma-separated animations coordinate repeated longhand lists with independent clocks, fill, pause, ordered completion, and last-contributing replace composition for the supported properties. Zero duration or zero iteration count now resolves a zero active duration with directed before/after fill, bounded positive or negative delay, ordered exactly-once completion, same-update mutation settlement, and no sustained frame demand. Identity interpolation from transform:none is part of the neutral forward/reverse contract. The candidate dynamic-style reduction passes Chrome and native and closes Bootstrap Modal's static-backdrop callback guard by retaining inline transition longhands through parser construction and recascade, honoring per-longhand importance, and exposing computed times in seconds. Native also passes candidate opacity and rotate() CSS Animations contracts, while generic discovery proves changing staggered-dot and spinner paint samples. Other animated properties, animation start/iteration/cancel event expansion, additive animation composition, more than eight live tracks per element, and the complete Web Animations API are not claimed." }, { "family": "clipping-and-rounded-borders", diff --git a/tests/WebPlatformSubset/contracts/css-animation-zero-time.html b/tests/WebPlatformSubset/contracts/css-animation-zero-time.html new file mode 100644 index 00000000..21607333 --- /dev/null +++ b/tests/WebPlatformSubset/contracts/css-animation-zero-time.html @@ -0,0 +1,109 @@ + + +Zero-duration and zero-count CSS animations settle without sustained frames + +
+
+
+ diff --git a/tests/WebPlatformSubset/webscene-component-profile.json b/tests/WebPlatformSubset/webscene-component-profile.json index c3854158..65694ee5 100644 --- a/tests/WebPlatformSubset/webscene-component-profile.json +++ b/tests/WebPlatformSubset/webscene-component-profile.json @@ -1902,6 +1902,13 @@ "evidence": ["css-animation-multiple-lists-672-242"], "reason": "WPT-aligned bounded reduction for coordinated animation lists, per-track clocks and state, supported-property replace composition, ordered completion, serialization, and cleanup." }, + { + "path": "contracts/css-animation-zero-time.html", + "type": "contract", + "capabilities": ["zero-duration-animation", "zero-iteration-count", "zero-active-duration", "direction-aware-zero-time-fill", "positive-and-negative-delay-phases", "paused-delay-resume", "ordered-exactly-once-animation-end", "same-update-settlement", "zero-sustained-frame-demand"], + "evidence": ["css-animation-zero-time-674-242"], + "reason": "WPT-aligned bounded reduction for zero active duration, directed opacity/rotation/filter fill, delayed and paused phases, ordered completion, CSSOM mutation, cleanup, and settled scheduling." + }, { "path": "contracts/css-filter-keyframes-temporal.html", "type": "contract", diff --git a/tests/WebPlatformSubset/webscene-css-animation-zero-time-profile.json b/tests/WebPlatformSubset/webscene-css-animation-zero-time-profile.json new file mode 100644 index 00000000..f81365d7 --- /dev/null +++ b/tests/WebPlatformSubset/webscene-css-animation-zero-time-profile.json @@ -0,0 +1,31 @@ +{ + "profile": "webscene-css-animation-zero-time-1", + "wptRevision": "2c705104a295c48053eeddf7fe0170d790a4e853", + "runtime": "v8", + "viewport": { + "width": 800, + "height": 600, + "deviceScaleFactor": 1 + }, + "required": [ + { + "path": "contracts/css-animation-zero-time.html", + "type": "contract", + "capabilities": [ + "zero-duration-animation", + "zero-iteration-count", + "zero-active-duration", + "direction-aware-zero-time-fill", + "positive-and-negative-delay-phases", + "paused-delay-resume", + "ordered-exactly-once-animation-end", + "same-update-settlement", + "zero-sustained-frame-demand" + ], + "evidence": ["css-animation-zero-time-674-242"], + "reason": "WPT-aligned bounded reduction for zero active duration, directed fill boundaries, delayed and paused phase changes, ordered completion, CSSOM mutation, cleanup, and settled scheduling." + } + ], + "harnessBlocked": [], + "excluded": [] +}