| <!DOCTYPE html> | |
| <link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-at-rule"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="/web-animations/testcommon.js"></script> | |
| <style> | |
| #scrollers > div { | |
| overflow: scroll; | |
| width: 100px; | |
| height: 100px; | |
| } | |
| #scrollers > div > div { | |
| height: 200px; | |
| } | |
| @keyframes expand { | |
| from { width: 100px; } | |
| to { width: 200px; } | |
| } | |
| #element { | |
| width: 0px; | |
| height: 20px; | |
| animation-name: expand; | |
| /* Some of the tests in this file assume animations attached to the | |
| DocumentTimeline are "stopped" without actually being paused. | |
| Using 600s + steps(10, end) achieves this for one minute.*/ | |
| animation-duration: 600s; | |
| animation-timing-function: steps(10, end); | |
| } | |
| </style> | |
| <div id=scrollers> | |
| <div id=scroller1><div></div></div> | |
| <div id=scroller2><div></div></div> | |
| </div> | |
| <div id=container></div> | |
| <script> | |
| // Force layout of scrollers. | |
| scroller1.offsetTop; | |
| scroller2.offsetTop; | |
| // Note the steps(10, end) timing function and height:100px. (10px scroll | |
| // resolution). | |
| scroller1.scrollTop = 20; | |
| scroller2.scrollTop = 40; | |
| function insertElement() { | |
| let element = document.createElement('div'); | |
| element.id = 'element'; | |
| container.append(element); | |
| return element; | |
| } | |
| function insertSheet(text) { | |
| let style = document.createElement('style'); | |
| style.textContent = text; | |
| container.append(style); | |
| return style; | |
| } | |
| // Insert an @scroll-timeline rule given 'options', where each option | |
| // has a reasonable default. | |
| function insertScrollTimeline(options) { | |
| if (typeof(options) == 'undefined') | |
| options = {}; | |
| if (typeof(options.name) == 'undefined') | |
| options.name = 'timeline'; | |
| if (typeof(options.source) == 'undefined') | |
| options.source = 'selector(#scroller1)'; | |
| if (typeof(options.start) == 'undefined') | |
| options.start = '0px'; | |
| if (typeof(options.end) == 'undefined') | |
| options.end = '100px'; | |
| return insertSheet(` | |
| @scroll-timeline ${options.name} { | |
| source: ${options.source}; | |
| start: ${options.start}; | |
| end: ${options.end}; | |
| } | |
| `); | |
| } | |
| // Runs a test with dynamically added/removed elements or CSS rules. | |
| // Each test is instantiated twice: once for the initial style resolve where | |
| // the DOM change was effectuated, and once after scrolling. | |
| function dynamic_rule_test(func, description) { | |
| // assert_width is an async function which verifies that the computed value | |
| // of 'width' is as expected. | |
| const instantiate = (assert_width, description) => { | |
| promise_test(async (t) => { | |
| try { | |
| await func(t, assert_width); | |
| } finally { | |
| while (container.firstChild) | |
| container.firstChild.remove(); | |
| } | |
| }, description); | |
| }; | |
| // Verify that the computed style is as expected immediately after the | |
| // rule change took place. | |
| instantiate(async (element, expected) => { | |
| assert_equals(getComputedStyle(element).width, expected); | |
| }, description + ' [immediate]'); | |
| // Verify that the computed style after scrolling a bit. | |
| instantiate(async (element, expected) => { | |
| scroller1.scrollTop = scroller1.scrollTop + 10; | |
| scroller2.scrollTop = scroller2.scrollTop + 10; | |
| await waitForNextFrame(); | |
| scroller1.scrollTop = scroller1.scrollTop - 10; | |
| scroller2.scrollTop = scroller2.scrollTop - 10; | |
| await waitForNextFrame(); | |
| assert_equals(getComputedStyle(element).width, expected); | |
| }, description + ' [scroll]'); | |
| } | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| // This element initially has a DocumentTimeline. | |
| await assert_width(element, '100px'); | |
| // Switch to scroll timeline. | |
| let sheet1 = insertScrollTimeline(); | |
| let sheet2 = insertSheet('#element { animation-timeline: timeline; }'); | |
| await assert_width(element, '120px'); | |
| // Switching from ScrollTimeline -> DocumentTimeline should preserve | |
| // current time. | |
| sheet1.remove(); | |
| sheet2.remove(); | |
| await assert_width(element, '120px'); | |
| }, 'Switching between document and scroll timelines'); | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| // Note: #scroller1 is at 20%, and #scroller2 is at 40%. | |
| insertScrollTimeline({name: 'timeline1', source: 'selector(#scroller1)'}); | |
| insertScrollTimeline({name: 'timeline2', source: 'selector(#scroller2)'}); | |
| insertSheet(` | |
| .tl1 { animation-timeline: timeline1; } | |
| .tl2 { animation-timeline: timeline2; } | |
| `); | |
| await assert_width(element, '100px'); | |
| element.classList.add('tl1'); | |
| await assert_width(element, '120px'); | |
| element.classList.add('tl2'); | |
| await assert_width(element, '140px'); | |
| element.classList.remove('tl2'); | |
| await assert_width(element, '120px'); | |
| // Switching from ScrollTimeline -> DocumentTimeline should preserve | |
| // current time. | |
| element.classList.remove('tl1'); | |
| await assert_width(element, '120px'); | |
| }, 'Changing computed value of animation-timeline changes effective timeline'); | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| insertScrollTimeline({source: 'selector(#scroller1)'}); | |
| insertSheet(` | |
| .scroll { animation-timeline: timeline; } | |
| .none { animation-timeline: none; } | |
| `); | |
| // DocumentTimeline applies by default. | |
| await assert_width(element, '100px'); | |
| // DocumentTimeline -> none | |
| element.classList.add('none'); | |
| await assert_width(element, '0px'); | |
| // none -> DocumentTimeline | |
| element.classList.remove('none'); | |
| await assert_width(element, '100px'); | |
| // DocumentTimeline -> ScrollTimeline | |
| element.classList.add('scroll'); | |
| await assert_width(element, '120px'); | |
| // ScrollTimeline -> none | |
| element.classList.add('none'); | |
| await assert_width(element, '0px'); | |
| // none -> ScrollTimeline | |
| element.classList.remove('none'); | |
| await assert_width(element, '120px'); | |
| }, 'Changing to/from animation-timeline:none'); | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| insertSheet('#element { animation-timeline: timeline; }'); | |
| // Unknown animation-timeline, current time held at zero. | |
| await assert_width(element, '100px'); | |
| insertScrollTimeline({source: 'selector(#scroller1)'}); | |
| await assert_width(element, '120px'); | |
| insertScrollTimeline({source: 'selector(#scroller2)'}); | |
| await assert_width(element, '140px'); | |
| }, 'Changing the source descriptor switches effective timeline'); | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| insertSheet('#element { animation-timeline: timeline; }'); | |
| // Unknown animation-timeline, current time held at zero. | |
| await assert_width(element, '100px'); | |
| insertScrollTimeline({start: '0px'}); | |
| await assert_width(element, '120px'); | |
| insertScrollTimeline({start: '50px'}); | |
| await assert_width(element, '0px'); | |
| }, 'Changing the start descriptor switches effective timeline'); | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| insertSheet('#element { animation-timeline: timeline; }'); | |
| // Unknown animation-timeline, current time held at zero. | |
| await assert_width(element, '100px'); | |
| insertScrollTimeline({end: '100px'}); | |
| await assert_width(element, '120px'); | |
| insertScrollTimeline({end: '10px'}); | |
| await assert_width(element, '0px'); | |
| }, 'Changing the end descriptor switches effective timeline'); | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| let reverse = insertSheet('#element { animation-direction: reverse; }'); | |
| insertSheet('#element { animation-timeline: timeline; }'); | |
| // Unknown animation-timeline, current time held at zero. | |
| await assert_width(element, '200px'); | |
| // Note: #scroller1 is at 20%. | |
| insertScrollTimeline({source: 'selector(#scroller1)'}); | |
| await assert_width(element, '180px'); | |
| // Note: #scroller1 is at 40%. | |
| insertScrollTimeline({source: 'selector(#scroller2)'}); | |
| await assert_width(element, '160px'); | |
| reverse.remove(); | |
| await assert_width(element, '140px'); | |
| }, 'Reverse animation direction'); | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| insertSheet('#element { animation-timeline: timeline; }'); | |
| // Unknown animation-timeline, current time held at zero. | |
| await assert_width(element, '100px'); | |
| // Note: #scroller1 is at 20%. | |
| insertScrollTimeline({source: 'selector(#scroller1)'}); | |
| await assert_width(element, '120px'); | |
| let paused = insertSheet('#element { animation-play-state: paused; }'); | |
| // We should still be at the same position after pausing. | |
| await assert_width(element, '120px'); | |
| // Note: #scroller1 is at 40%. | |
| insertScrollTimeline({source: 'selector(#scroller2)'}); | |
| // Even when switching timelines, we should be at the same position until | |
| // we unpause. | |
| await assert_width(element, '120px'); | |
| // Unpausing should synchronize to the scroll position. | |
| paused.remove(); | |
| await assert_width(element, '140px'); | |
| }, 'Switching timelines while paused'); | |
| dynamic_rule_test(async (t, assert_width) => { | |
| let element = insertElement(); | |
| // Note: #scroller1 is at 20%. | |
| insertScrollTimeline({source: 'selector(#scroller1)'}); | |
| await assert_width(element, '100px'); | |
| insertSheet(`#element { | |
| animation-timeline: timeline; | |
| animation-play-state: paused; | |
| }`); | |
| // Pausing should happen before the timeline is modified. (Tentative). | |
| // https://github.com/w3c/csswg-drafts/issues/5653 | |
| await assert_width(element, '100px'); | |
| insertSheet('#element { animation-play-state: running; }'); | |
| await assert_width(element, '120px'); | |
| }, 'Switching timelines and pausing at the same time'); | |
| </script> |