diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/scroll-animations/scroll-timelines/scroll-animation-effect-phases.tentative.html | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/scroll-animations/scroll-timelines/scroll-animation-effect-phases.tentative.html')
-rw-r--r-- | testing/web-platform/tests/scroll-animations/scroll-timelines/scroll-animation-effect-phases.tentative.html | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/testing/web-platform/tests/scroll-animations/scroll-timelines/scroll-animation-effect-phases.tentative.html b/testing/web-platform/tests/scroll-animations/scroll-timelines/scroll-animation-effect-phases.tentative.html new file mode 100644 index 0000000000..41ae0e0612 --- /dev/null +++ b/testing/web-platform/tests/scroll-animations/scroll-timelines/scroll-animation-effect-phases.tentative.html @@ -0,0 +1,555 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Verify timeline time, animation time, effect time, and effect progress for all timeline states: before start, at start, in range, at end, after end while using various effect delay values</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/web-animations/testcommon.js"></script> +<script src="testcommon.js"></script> +<style> + .scroller { + overflow: hidden; + height: 200px; + width: 200px; + } + .contents { + /* Make scroll range 1000 to simplify the math and avoid rounding errors */ + height: 1200px; + width: 100%; + } +</style> +<div id="log"></div> +<script> + 'use strict'; + // Note: effects are scaled to fill the timeline. + + // Each entry is [[test input], [test expectations]] + // test input = ["description", delay, end_delay, scroll percent] + // test expectations = [timeline time, animation current time, + // effect local time, effect progress, effect phase, + // opacity] + + /* All interesting transitions: + at timeline start + before effect delay + at effect start + in active range + at effect end + after effect end + at timeline end + */ + const test_cases = [ + // Case 1: No delays. + // Boundary at end of active phase is inclusive. + [ + ["at start", 0, 0, 0], + [0, 0, 0, 0, "active", 0.3] + ], + [ + ["in active range", 0, 0, 0.50], + [50, 50, 50, 0.5, "active", 0.5] + ], + [ + ["at effect end time", 0, 0, 1.0], + [100, 100, 100, 1.0, "active", 0.7] + ], + + // Case 2: Positive start delay and no end delay. + // Boundary at end of active phase is inclusive. + [ + ["at timeline start", 500, 0, 0], + [0, 0, 0, null, "before", 1] + ], + [ + ["before start delay", 500, 0, 0.25], + [25, 25, 25, null, "before", 1] + ], + [ + ["at start delay", 500, 0, 0.5], + [50, 50, 50, 0, "active", 0.3] + ], + [ + ["in active range", 500, 0, 0.75], + [75, 75, 75, 0.5, "active", 0.5] + ], + [ + ["at effect end time", 500, 0, 1.0], + [100, 100, 100, 1.0, "active", 0.7] + ], + + // case 3: No start delay, Positive end delay. + // Boundary at end of active phase is exclusive. + [ + ["at timeline start", 0, 500, 0], + [0, 0, 0, 0, "active", 0.3] + ], + [ + ["in active range", 0, 500, 0.25], + [25, 25, 25, 0.5, "active", 0.5] + ], + [ + ["at effect end time", 0, 500, 0.5], + [50, 50, 50, null, "after", 1.0] + ], + [ + ["after effect end time", 0, 500, 0.75], + [75, 75, 75, null, "after", 1.0] + ], + [ + ["at timeline boundary", 0, 500, 1.0], + [100, 100, 100, null, "after", 1.0] + ], + + // case 4: Positive start and end delays. + // Boundary at end of active phase is exclusive. + [ + ["at timeline start", 250, 250, 0], + [0, 0, 0, null, "before", 1] + ], + [ + ["before start delay", 250, 250, 0.1], + [10, 10, 10, null, "before", 1] + ], + [ + ["at start delay", 250, 250, 0.25], + [25, 25, 25, 0, "active", 0.3] + ], + [ + ["in active range", 250, 250, 0.5], + [50, 50, 50, 0.5, "active", 0.5] + ], + [ + ["at effect end time", 250, 250, 0.75], + [75, 75, 75, null, "after", 1.0] + ], + [ + ["after effect end time", 250, 250, 0.9], + [90, 90, 90, null, "after", 1.0] + ], + [ + ["at timeline boundary", 250, 250, 1.0], + [100, 100, 100, null, "after", 1.0] + ], + + // Case 5: Negative start and end delays. + // Effect boundaries are not reachable. + [ + ["at timeline start", -125, -125, 0], + [0, 0, 0, 0.25, "active", 0.4] + ], + [ + ["in active range", -125, -125, 0.5], + [50, 50, 50, 0.5, "active", 0.5] + ], + [ + ["at timeline end", -125, -125, 1.0], + [100, 100, 100, 0.75, "active", 0.6] + ] + ]; + + for (const test_case of test_cases) { + const [inputs, expected] = test_case; + const [test_name, delay, end_delay, scroll_percentage] = inputs; + + const description = `Current times and effect phase ${test_name} when` + + ` delay = ${delay} and endDelay = ${end_delay} |`; + + promise_test( + create_scroll_timeline_delay_test( + delay, end_delay, scroll_percentage, expected), + description); + } + + function create_scroll_timeline_delay_test( + delay, end_delay, scroll_percentage, expected){ + return async t => { + const target = createDiv(t); + const timeline = createScrollTimeline(t); + const effect = new KeyframeEffect( + target, + { + opacity: [0.3, 0.7] + }, + { + duration: 500, + delay: delay, + endDelay: end_delay + } + ); + const animation = new Animation(effect, timeline); + t.add_cleanup(() => { + animation.cancel(); + }); + const scroller = timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + animation.play(); + + await animation.ready; + + scroller.scrollTop = scroll_percentage * maxScroll; + + // Wait for new animation frame which allows the timeline to compute + // new current time. + await waitForNextFrame(); + + const [expected_timeline_current_time, + expected_animation_current_time, + expected_effect_local_time, + expected_effect_progress, + expected_effect_phase, + expected_opacity] = expected; + + assert_percents_equal( + animation.timeline.currentTime, + expected_timeline_current_time, + "timeline current time"); + assert_percents_equal( + animation.currentTime, + expected_animation_current_time, + "animation current time"); + assert_percents_equal( + animation.effect.getComputedTiming().localTime, + expected_effect_local_time, + "animation effect local time"); + assert_approx_equals_or_null( + animation.effect.getComputedTiming().progress, + expected_effect_progress, + 0.001, + "animation effect progress"); + assert_phase( + animation, expected_effect_phase); + assert_approx_equals( + parseFloat(getComputedStyle(target).opacity), expected_opacity, + 0.001, + 'target opacity'); + } + } + + function createKeyframeEffectOpacity(test){ + return new KeyframeEffect( + createDiv(test), + { + opacity: [0.3, 0.7] + }, + { + duration: 1000 + } + ); + } + + function verifyEffectBeforePhase(animation) { + // If currentTime is null, we are either idle, or running with an + // inactive timeline. Either way, the animation is not in effect and cannot + // be in the before phase. + assert_true(animation.currentTime != null, + 'Animation is not in effect'); + + const fillMode = animation.effect.getTiming().fill; + animation.effect.updateTiming({ fill: 'none' }); + + // progress == null AND opacity == 1 implies we are in the effect before + // or after phase. + assert_equals(animation.effect.getComputedTiming().progress, null); + assert_equals( + window.getComputedStyle(animation.effect.target) + .getPropertyValue("opacity"), + "1"); + + // If the progress is no longer null after adding fill: backwards, then we + // are in the before phase. + animation.effect.updateTiming({ fill: 'backwards' }); + assert_true(animation.effect.getComputedTiming().progress != null); + assert_equals( + window.getComputedStyle(animation.effect.target) + .getPropertyValue("opacity"), + "0.3"); + + // Reset fill mode to avoid side-effects. + animation.effect.updateTiming({ fill: fillMode }); + } + + function createScrollLinkedOpacityAnimationWithDelays(t) { + const animation = new Animation( + createKeyframeEffectOpacity(t), + createScrollTimeline(t) + ); + t.add_cleanup(() => { + animation.cancel(); + }); + animation.effect.updateTiming({ + duration: 1000, + delay: 500, + endDelay: 500 + }); + return animation; + } + + + promise_test(async t => { + const animation = createScrollLinkedOpacityAnimationWithDelays(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + // scroll pos + // current time + // start time + // | + // |---- 25% before ----|---- 50% active ----|---- 25% after ----| + animation.play(); + await animation.ready; + assert_percents_equal(animation.startTime, 0); + assert_phase(animation, 'before'); + + // start time scroll pos + // | current time + // | | + // |---- 25% before ----|---- 50% active ----|---- 25% after ----| + scroller.scrollTop = 0.5 * maxScroll; + await waitForNextFrame(); + assert_phase(animation, 'active'); + + // start time scroll pos current time + // | | | + // |---- 25% before ----|---- 50% active ----|---- 25% after ----| + animation.playbackRate = 2; + assert_phase(animation, 'after'); + + // start time scroll pos current time + // | | | + // |---- 33.3% before ----|---- 66.7% active ---------------------| + animation.effect.updateTiming({ endDelay: 0 }); + assert_phase(animation, 'active'); + + // scroll pos start time + // current time | + // | | + // |---- 33.3% before ----|---- 66.7% active ----------------------| + animation.playbackRate = -1; + assert_percents_equal(animation.startTime, 100); + assert_phase(animation, 'active'); + + // start time + // scroll pos current time + // | | | + // |---- 33.3% before ----|---- 66.7% active -----------------------| + animation.playbackRate = -2; + assert_phase(animation, 'active'); + + // current time start time + // | scroll pos + // | | + // |---- 33.3% before ----|---- 66.7% active -----------------------| + scroller.scrollTop = maxScroll; + await waitForNextFrame(); + assert_phase(animation, 'before'); + + // current time start time + // | scroll pos + // | | + // |--------------------- 100% active -------------------------------| + animation.effect.updateTiming({ delay: 0 }); + assert_phase(animation, 'active'); + + // Finally, switch to a document timeline. The before-active boundary + // becomes exclusive. + animation.timeline = document.timeline; + animation.currentTime = 0; + await waitForNextFrame(); + assert_phase(animation, 'before'); + + }, 'Playback rate affects whether active phase boundary is inclusive.'); + + promise_test(async t => { + const animation = createScrollLinkedOpacityAnimationWithDelays(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + animation.play(); + await animation.ready; + verifyEffectBeforePhase(animation); + + animation.pause(); + await waitForNextFrame(); + verifyEffectBeforePhase(animation); + + animation.play(); + await waitForNextFrame(); + + verifyEffectBeforePhase(animation); + }, 'Verify that (play -> pause -> play) doesn\'t change phase/progress.'); + + promise_test(async t => { + const animation = createScrollLinkedOpacityAnimationWithDelays(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + animation.play(); + await animation.ready; + verifyEffectBeforePhase(animation); + + animation.pause(); + await animation.ready; + verifyEffectBeforePhase(animation); + + // Scrolling should not cause the animation effect to change. + scroller.scrollTop = 0.5 * maxScroll; + await waitForNextFrame(); + + // Check timeline phase + assert_percents_equal(animation.timeline.currentTime, 50); + assert_percents_equal(animation.currentTime, 0); + assert_percents_equal(animation.effect.getComputedTiming().localTime, 0, + "effect local time"); + + // Make sure the effect is still in the before phase even though the + // timeline is not. + verifyEffectBeforePhase(animation); + }, 'Pause in before phase, scroll timeline into active phase, animation ' + + 'should remain in the before phase'); + + promise_test(async t => { + const animation = createScrollLinkedOpacityAnimationWithDelays(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + animation.play(); + await animation.ready; + verifyEffectBeforePhase(animation); + + animation.pause(); + await waitForNextFrame(); + verifyEffectBeforePhase(animation); + + // Setting the current time should force the animation into effect. + const expected_time = 50; + animation.currentTime = CSS.percent(expected_time); + await waitForNextFrame(); + assert_percents_equal(animation.timeline.currentTime, 0); + assert_percents_equal(animation.currentTime, expected_time, + 'Current time matches set value'); + assert_percents_equal( + animation.effect.getComputedTiming().localTime, + expected_time, "Effect local time after setting animation.currentTime"); + assert_equals(animation.effect.getComputedTiming().progress, 0.5, + "Progress after setting animation.currentTime"); + assert_equals( + window.getComputedStyle(animation.effect.target) + .getPropertyValue("opacity"), + "0.5", "Opacity after setting animation.currentTime"); + + // Scrolling should not cause the animation effect to change since + // paused. + scroller.scrollTop = 0.75 * maxScroll; // scroll so that timeline is 75% + await waitForNextFrame(); + assert_percents_equal(animation.timeline.currentTime, 75); + + // animation and effect timings are unchanged. + assert_percents_equal(animation.currentTime, expected_time, + "Current time after scrolling while paused"); + assert_percents_equal( + animation.effect.getComputedTiming().localTime, + expected_time, + "Effect local time after scrolling while paused"); + assert_equals(animation.effect.getComputedTiming().progress, 0.5, + "Progress after scrolling while paused"); + assert_equals( + window.getComputedStyle(animation.effect.target) + .getPropertyValue("opacity"), + "0.5", "Opacity after scrolling while paused"); + }, 'Pause in before phase, set animation current time to be in active ' + + 'range, animation should become active. Scrolling should have no effect.'); + + promise_test(async t => { + const animation = createScrollLinkedOpacityAnimationWithDelays(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + animation.play(); + await animation.ready; + + // Causes the timeline to be inactive + scroller.style.overflow = "visible"; + await waitForNextFrame(); + await waitForNextFrame(); + + // Verify that he timeline is inactive + assert_equals(animation.timeline.currentTime, null, + "Timeline is inactive"); + assert_equals( + animation.currentTime, null, + "Current time for running animation with an inactive timeline"); + assert_equals(animation.effect.getComputedTiming().localTime, null, + "effect local time with inactive timeline"); + + // Setting the current time while timeline is inactive should pause the + // animation at the specified time. + animation.currentTime = CSS.percent(50); + await waitForNextFrame(); + await waitForNextFrame(); + + // Verify that animation currentTime is properly set despite the inactive + // timeline. + assert_equals(animation.timeline.currentTime, null); + assert_percents_equal(animation.currentTime, 50); + assert_percents_equal(animation.effect.getComputedTiming().localTime, 50, + "effect local time after setting animation current time"); + + // Check effect phase + // progress == 0.5 AND opacity == 0.5 shows we are in the effect active + // phase. + assert_equals(animation.effect.getComputedTiming().progress, 0.5, + "effect progress"); + assert_equals( + window.getComputedStyle(animation.effect.target) + .getPropertyValue("opacity"), + "0.5", + "effect opacity after setting animation current time"); + }, 'Make scroller inactive, then set current time to an in range time'); + + promise_test(async t => { + const animation = createScrollLinkedOpacityAnimationWithDelays(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + scroller.scrollTop = 0.5 * maxScroll; + // Update timeline.currentTime. + await waitForNextFrame(); + + animation.pause(); + await animation.ready; + // verify effect is applied. + const expected_progress = 0.5; + assert_equals( + animation.effect.getComputedTiming().progress, + expected_progress, + "Verify effect progress after pausing."); + + // cause the timeline to become inactive + scroller.style.overflow = 'visible'; + await waitForAnimationFrames(2); + assert_equals(animation.timeline.currentTime, null, + 'Sanity check the timeline is inactive.'); + assert_equals( + animation.effect.getComputedTiming().progress, + expected_progress, + "Verify effect progress after the timeline goes inactive."); + }, 'Animation effect is still applied after pausing and making timeline ' + + 'inactive.'); + + promise_test(async t => { + const animation = createScrollLinkedOpacityAnimationWithDelays(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + animation.play(); + await animation.ready; + + // cause the timeline to become inactive + scroller.style.overflow = 'visible'; + + scroller.scrollTop; + + animation.pause(); + }, 'Make timeline inactive, force style update then pause the animation. ' + + 'No crashing indicates test success.'); +</script> |