diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:42 +0000 |
commit | da4c7e7ed675c3bf405668739c3012d140856109 (patch) | |
tree | cdd868dba063fecba609a1d819de271f0d51b23e /testing/web-platform/tests/web-animations | |
parent | Adding upstream version 125.0.3. (diff) | |
download | firefox-da4c7e7ed675c3bf405668739c3012d140856109.tar.xz firefox-da4c7e7ed675c3bf405668739c3012d140856109.zip |
Adding upstream version 126.0.upstream/126.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/web-animations')
10 files changed, 572 insertions, 1 deletions
diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/progress.tentative.html b/testing/web-platform/tests/web-animations/interfaces/Animation/progress.tentative.html new file mode 100644 index 0000000000..38f37cbfeb --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/progress.tentative.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.progress</title> +<link rel="help" +href="https://drafts.csswg.org/web-animations-2/#the-progress-of-an-animation"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<body> +<div id="log"></div> +<script> +'use strict'; + + +test(t => { + const animation = new Animation(null); + animation.startTime = document.timeline.currentTime; + assert_time_equals_literal(animation.currentTime, 0, 'currentTime is zero'); + assert_equals(animation.progress, null, 'progress is unresolved'); +}, 'progress of a newly created animation without an effect is unresolved'); + +test(t => { + // currentTime should be unresolved because the animation has no associated + // timeline. + const animation = new Animation(new KeyframeEffect(createDiv(t), null), null); + assert_equals(animation.currentTime, null, 'currentTime is unresolved'); + assert_equals(animation.progress, null, 'progress is unresolved'); +}, 'progress of an animation whose currentTime is unresolved is unresolved.'); + +test(t => { + const animation = new Animation(new KeyframeEffect(createDiv(t), null, + { duration: 0 }), document.timeline); + // Set the startTime to 20 seconds into the future. + animation.startTime = document.timeline.currentTime + 20 * MS_PER_SEC; + assert_less_than(animation.currentTime, 0, 'currentTime is negative'); + assert_approx_equals(animation.progress, 0, 0.01, 'progress is zero'); +}, "progress of an animation whose effect's endTime is zero is zero if its " + + "currentTime is negative."); + +test(t => { + const animation = new Animation(new KeyframeEffect(createDiv(t), null, + { duration: 0 }), document.timeline); + animation.startTime = document.timeline.currentTime; + assert_time_equals_literal(animation.currentTime, 0, 'currentTime is zero'); + assert_approx_equals(animation.progress, 1, 0.01, 'progress is one'); + + animation.startTime = document.timeline.currentTime - 20 * MS_PER_SEC; + assert_greater_than(animation.currentTime, 0, 'currentTime greater than zero'); + assert_approx_equals(animation.progress, 1, 0.01, 'progress is one'); +}, "progress of an animation whose effect's endTime is zero is one if its " + + "currentTime is non-negative."); + +test(t => { + const animation = new Animation(new KeyframeEffect(createDiv(t), null, + { duration: Infinity }), document.timeline); + + animation.startTime = document.timeline.currentTime - 20 * MS_PER_SEC; + assert_greater_than(animation.currentTime, 0, 'currentTime is positive'); + assert_approx_equals(animation.progress, 0, 0.01, 'progress is zero'); +}, "progress of an animation whose effect's endTime is infinity is zero."); + +test(t => { + const animation = new Animation(new KeyframeEffect(createDiv(t), null, + 200 * MS_PER_SEC), document.timeline); + animation.playbackRate = 2; + + animation.startTime = document.timeline.currentTime - 50 * MS_PER_SEC; + assert_time_equals_literal(animation.currentTime, 100 * MS_PER_SEC, 'currentTime is 100s'); + assert_approx_equals(animation.progress, 0.5, 0.01, 'progress is zero'); + + animation.startTime = document.timeline.currentTime - 100 * MS_PER_SEC; + assert_time_equals_literal(animation.currentTime, 200 * MS_PER_SEC, 'currentTime is 200s'); + assert_approx_equals(animation.progress, 1, 0.01, 'progress is one'); + + animation.startTime = document.timeline.currentTime - 150 * MS_PER_SEC; + assert_time_equals_literal(animation.currentTime, 300 * MS_PER_SEC, 'currentTime is 300s'); + assert_approx_equals(animation.progress, 1, 0.01, 'progress is still one'); +}, "progress of an animation is calculated by currentTime / effect endTime."); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/scroll-timeline-progress.tentative.html b/testing/web-platform/tests/web-animations/interfaces/Animation/scroll-timeline-progress.tentative.html new file mode 100644 index 0000000000..b40e72482b --- /dev/null +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/scroll-timeline-progress.tentative.html @@ -0,0 +1,166 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Animation.progress</title> +<link rel="help" +href="https://drafts.csswg.org/web-animations-2/#the-progress-of-an-animation"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../../testcommon.js"></script> +<script src="../../../scroll-animations/scroll-timelines/testcommon.js"></script> +<body> + <style> + .scroller { + overflow-x: hidden; + overflow-y: auto; + height: 200px; + width: 100px; + will-change: transform; + } + .contents { + height: 1200px; + width: 100%; + } + #target { + height: 100px; + width: 100px; + background-color: green; + margin-top: -1000px; + } + </style> +<div id="log"></div> +<script> +'use strict'; + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + assert_equals(animation.currentTime, null, + "The current time is null in Idle state."); + assert_equals(animation.progress, null, + "The progress is null since the currentTime is unresolved."); + + animation.play(); + assert_true(animation.pending, "Animation is in the pending state."); + assert_equals(animation.currentTime, null, + "The current time remains null while in the pending state."); + assert_equals(animation.progress, null, + "The progress is null since the currentTime is unresolved."); + + await animation.ready; + // Verify initial start and current times once ready. + assert_percents_equal(animation.currentTime, 0, + "The current time is resolved when ready."); + assert_equals(animation.progress, 0, "The progress is should be zero."); + + scroller.scrollTop = 0.4 * maxScroll; + + await waitForNextFrame(); + assert_percents_equal(animation.currentTime, 40, + "currentTime reflects progress as a percentage"); + assert_approx_equals(animation.progress, 0.4, 0.01, + "The progress is should match the scroll progress."); +}, "animation.progress reflects the progress of a scroll animation as a "+ + "number between 0 and 1"); + +promise_test(async t => { + const animation = createScrollLinkedAnimation(t); + const scroller = animation.timeline.source; + const maxScroll = scroller.scrollHeight - scroller.clientHeight; + + animation.play(); + await animation.ready; + // Verify initial start and current times once ready. + assert_percents_equal(animation.currentTime, 0, + "The current time is resolved when ready."); + assert_equals(animation.progress, 0, "The progress is should be zero."); + + scroller.scrollTop = 0.4 * maxScroll; + await waitForNextFrame(); + + let timing = animation.effect.getComputedTiming(); + // iteration duration should be 100%, progress should reflect 40%. + assert_percents_equal(timing.duration, 100); + assert_percents_equal(animation.currentTime, 40, + "currentTime reflects progress as a percentage"); + assert_approx_equals(animation.progress, 0.4, 0.01, + "The progress is should match the scroll progress."); + timing = animation.effect.getComputedTiming(); + + animation.effect.updateTiming({ iterations: 2 }); + + timing = animation.effect.getComputedTiming(); + // iteration duration should be 50%, progress should still reflect 40% + // as it measures currentTime / effect endTime. + assert_percents_equal(timing.duration, 50); + assert_percents_equal(animation.currentTime, 40, + "currentTime reflects progress as a percentage"); + assert_approx_equals(animation.progress, 0.4, 0.01, + "The progress is should match the scroll progress."); +}, "animation.progress reflects the overall progress of a scroll animation " + + "with multiple iterations."); + +promise_test(async t => { + const scroller = createScroller(t); + const view_timeline = createViewTimeline(t); + + const animation = createAnimation(t); + animation.rangeStart = { rangeName: 'contain', offset: CSS.percent(10) }; + animation.rangeEnd = { rangeName: 'contain', offset: CSS.percent(90) }; + animation.timeline = view_timeline; + + await animation.ready; + + // Cover range is [0, 300] + // Contain range is [100, 200] + // rangeStart offset of 10% => 110px + // rangeEnd offset of 90% => 190px + const target_pct = 40; + const target_scroll_top = 110 + (target_pct / 100) * (190 - 110); + scroller.scrollTop = target_scroll_top; + await waitForNextFrame(); + + const start_time = 110 / 300 * 100; + const timeline_time = target_scroll_top / 300 * 100; + const expected_current_time = timeline_time - start_time; + assert_percents_equal(animation.currentTime, expected_current_time, + "currentTime reflects progress as a percentage"); + assert_approx_equals(animation.progress, target_pct / 100, 0.01, + "progress should reflect fraction of view timeline range scroll through."); +}, "animation.progress reflects the overall progress of a scroll animation " + + "that uses a view-timeline."); + +promise_test(async t => { + const scroller = createScroller(t); + const view_timeline = createViewTimeline(t); + + const animation = createAnimation(t); + animation.rangeStart = { rangeName: 'contain', offset: CSS.percent(10) }; + animation.rangeEnd = { rangeName: 'contain', offset: CSS.percent(90) }; + animation.timeline = view_timeline; + + await animation.ready; + + // Cover range is [0, 300] + // Contain range is [100, 200] + // rangeStart offset of 10% => 110px + // rangeEnd offset of 10% => 190px + + scroller.scrollTop = 100; + await waitForNextFrame(); + let timing = animation.effect.getComputedTiming(); + assert_less_than(animation.currentTime.value, 0, "currentTime is negative"); + assert_approx_equals(animation.progress, 0, 0.01, "progress is zero when " + + "scroll offset is less than range start."); + + scroller.scrollTop = 200; + await waitForNextFrame(); + assert_approx_equals(animation.currentTime.value, timing.endTime.value, 0.01, + "currentTime has reached endTime"); + assert_approx_equals(animation.progress, 1, 0.01, "progress is one when " + + "scroll offset goes past than range end."); +}, "progresss of a view-timeline is bounded between 0 and 1."); + +</script> +</body> diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html b/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html index 0ec21657e3..d2d6e9d4c9 100644 --- a/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html +++ b/testing/web-platform/tests/web-animations/interfaces/Animation/style-change-events.html @@ -169,6 +169,7 @@ const tests = { // no effect. rangeStart: UsePropertyTest(animation => animation.rangeStart), rangeEnd: UsePropertyTest(animation => animation.rangeEnd), + progress: UsePropertyTest(animation => animation.progress), replaceState: UsePropertyTest(animation => animation.replaceState), ready: UsePropertyTest(animation => animation.ready), finished: UsePropertyTest(animation => { diff --git a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html index 271a47b301..2252248e23 100644 --- a/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html +++ b/testing/web-platform/tests/web-animations/interfaces/KeyframeEffect/processing-a-keyframes-argument-001.html @@ -37,7 +37,6 @@ const gNonAnimatableProps = [ 'transitionTimingFunction', 'contain', 'direction', - 'display', 'textCombineUpright', 'textOrientation', 'unicodeBidi', diff --git a/testing/web-platform/tests/web-animations/responsive/background-position-responsive.html b/testing/web-platform/tests/web-animations/responsive/background-position-responsive.html new file mode 100644 index 0000000000..7b1a6cd4db --- /dev/null +++ b/testing/web-platform/tests/web-animations/responsive/background-position-responsive.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Animation with neutral keyframe is responsive to change in underlying style</title> + <link rel="help" href="https://www.w3.org/TR/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="responsive-test.js"></script> +</head> +<body></body> +<script type="text/javascript"> + promise_test(async t => { + const responsiveTest = + createResponsiveTest(t, { + property: 'backgroundPosition', + to: '100px 100px' + }); + await responsiveTest.ready; + responsiveTest.underlyingValue = '20px 60px'; + responsiveTest.assertResponsive([ + {at: 0.25, is: '40px 70px'}, + {at: 0.75, is: '80px 90px'}, + ]); + responsiveTest.underlyingValue = '60px 20px'; + responsiveTest.assertResponsive([ + {at: 0.25, is: '70px 40px'}, + {at: 0.75, is: '90px 80px'}, + ]); + }, 'Animating from a neutral keyframe when the underlying style is ' + + 'explicitly set'); + + promise_test(async t => { + const responsiveTest = + createResponsiveTest(t, { + property: 'backgroundPosition', + from: 'inherit', + to: '100px 100px' + }); + await responsiveTest.ready; + responsiveTest.inheritedValue = '20px 60px'; + responsiveTest.assertResponsive([ + {at: 0.25, is: '40px 70px'}, + {at: 0.75, is: '80px 90px'}, + ]); + responsiveTest.inheritedValue = '60px 20px'; + responsiveTest.assertResponsive([ + {at: 0.25, is: '70px 40px'}, + {at: 0.75, is: '90px 80px'}, + ]); + }, 'Animating from a neutral keyframe when the underlying style is ' + + 'inherited from the parent'); +</script> diff --git a/testing/web-platform/tests/web-animations/responsive/box-shadow-responsive.html b/testing/web-platform/tests/web-animations/responsive/box-shadow-responsive.html new file mode 100644 index 0000000000..4f5325dca6 --- /dev/null +++ b/testing/web-platform/tests/web-animations/responsive/box-shadow-responsive.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Animation with neutral keyframe is responsive to change in underlying style</title> + <link rel="help" href="https://www.w3.org/TR/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="responsive-test.js"></script> +</head> +<body></body> +<script type="text/javascript"> + promise_test(async t => { + const responsiveTest = + createResponsiveTest(t, { + property: 'boxShadow', + from: 'green 20px 20px 20px 20px', + to: 'inherit', + }); + await responsiveTest.ready; + responsiveTest.inheritedValue = 'blue 0px 0px 0px 0px'; + responsiveTest.assertResponsive([ + {at: 0.25, is: 'rgb(0, 96, 64) 15px 15px 15px 15px'}, + {at: 0.75, is: 'rgb(0, 32, 191) 5px 5px 5px 5px'}, + ]); + responsiveTest.inheritedValue = 'yellow 100px 100px 100px 100px'; + responsiveTest.assertResponsive([ + {at: 0.25, is: 'rgb(64, 160, 0) 40px 40px 40px 40px'}, + {at: 0.75, is: 'rgb(191, 223, 0) 80px 80px 80px 80px'}, + ]); + }, 'Animating to inherit responsive to change in style'); + + promise_test(async t => { + const responsiveTest = + createResponsiveTest(t, { + property: 'boxShadow', + from: 'inherit', + to: 'green 20px 20px 20px 20px', + }); + await responsiveTest.ready; + responsiveTest.inheritedValue = + 'blue 0px 0px 0px 0px, yellow 100px 100px 100px 100px'; + responsiveTest.assertResponsive([ + { + at: 0.25, + is: 'rgb(0, 32, 191) 5px 5px 5px 5px, ' + + 'rgba(255, 255, 0, 0.75) 75px 75px 75px 75px' + }, + { + at: 0.75, + is: 'rgb(0, 96, 64) 15px 15px 15px 15px, ' + + 'rgba(255, 255, 0, 0.25) 25px 25px 25px 25px' + }, + ]); + responsiveTest.inheritedValue = 'yellow 100px 100px 100px 100px'; + responsiveTest.assertResponsive([ + {at: 0.25, is: 'rgb(191, 223, 0) 80px 80px 80px 80px'}, + {at: 0.75, is: 'rgb(64, 160, 0) 40px 40px 40px 40px'}, + ]); + }, 'Animating from inherit responsive to change in style'); +</script> diff --git a/testing/web-platform/tests/web-animations/responsive/neutral-keyframe-ref.html b/testing/web-platform/tests/web-animations/responsive/neutral-keyframe-ref.html new file mode 100644 index 0000000000..3893330fb9 --- /dev/null +++ b/testing/web-platform/tests/web-animations/responsive/neutral-keyframe-ref.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <style type="text/css"> + #block { + background-color: green; + height: 100px; + width: 100px; + transform: translate(100px); + } + </style> +</head> +<body> + <div id="block"></div> +</body> +</html> diff --git a/testing/web-platform/tests/web-animations/responsive/neutral-keyframe.html b/testing/web-platform/tests/web-animations/responsive/neutral-keyframe.html new file mode 100644 index 0000000000..813aa7c04c --- /dev/null +++ b/testing/web-platform/tests/web-animations/responsive/neutral-keyframe.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>Animation with neutral keyframe is responsive to change in underlying style</title> + <link rel="help" href="https://www.w3.org/TR/web-animations-1/#the-effect-value-of-a-keyframe-animation-effect"> + <link rel="match" href="neutral-keyframe-ref.html"> + <script src="/common/reftest-wait.js"></script> + <script src="/web-animations/testcommon.js"></script> + <style type="text/css"> + #block { + background-color: green; + height: 100px; + width: 100px; + } + </style> +</head> +<body> + <div id="block"></div> +</body> +<script> + window.onload = async () => { + const target = document.getElementById('block'); + const anim = target.animate({ translate: '200px' }, + { + duration: 10000, + easing: 'steps(1,jump-end)' + }); + await anim.ready; + await waitForNextFrame(); + // The neutral keyframe value changes from transform 'none' to '100px'. + // Since using jump-end for the easing function, the animated value is the + // underlying (neutral) value. If the box is not translated, then the change + // to the underlying value is not taking effect. + target.style.translate = '100px'; + await waitForNextFrame(); + takeScreenshot(); + }; +</script> +</html> diff --git a/testing/web-platform/tests/web-animations/responsive/responsive-test.js b/testing/web-platform/tests/web-animations/responsive/responsive-test.js new file mode 100644 index 0000000000..feca53259c --- /dev/null +++ b/testing/web-platform/tests/web-animations/responsive/responsive-test.js @@ -0,0 +1,65 @@ +class ResponsiveTest { + constructor(target, property, keyframes) { + this.property = property; + this.target = target; + this.duration = 1000; + this.anim = target.animate(keyframes, this.duration); + this.anim.pause(); + } + + get ready() { + return new Promise(resolve => { + this.anim.ready.then(resolve); + }); + } + + set underlyingValue(value) { + this.target.style[this.property] = value; + } + + set inheritedValue(value) { + this.target.parentElement.style[this.property] = value; + } + + // The testCases are of the form: + // [{at: <fractional_progress>, is: <computed style> }, ...] + assertResponsive(testCases) { + for (let i = 0; i < testCases.length; i++) { + const testCase = testCases[i]; + this.anim.currentTime = this.duration * testCase.at; + assert_equals(getComputedStyle(this.target)[this.property], testCase.is, + `${this.property} at ${testCase.at}`); + } + } +} + +// Creates a test that allows setting the underlying style of the target +// element or its parent. +// Options are of the form: +// property: required property in camelcase form as used in the +// web animation API. +// from: optional starting keyframe as a string. +// to: optional ending keyframe as a string. +function createResponsiveTest(test, options) { + const parent = document.createElement('div'); + const target = document.createElement('div'); + document.body.appendChild(parent); + parent.appendChild(target); + const property = options.property; + const keyframes = []; + const createKeyframe = (value) => { + const keyframe = {}; + keyframe[property] = value; + return keyframe; + } + if (options.from) { + keyframes.push(createKeyframe(options.from)); + } + if (options.to) { + keyframes.push(createKeyframe(options.to)); + } + test.add_cleanup(() => { + parent.remove(); + }); + return new ResponsiveTest(target, property, keyframes); +} diff --git a/testing/web-platform/tests/web-animations/responsive/width.html b/testing/web-platform/tests/web-animations/responsive/width.html new file mode 100644 index 0000000000..9f5bf5a29d --- /dev/null +++ b/testing/web-platform/tests/web-animations/responsive/width.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + +body { width: 500px } + +#child { width: 20px } + +</style> + +<div id='container'> + <div id='element'> + <div id='child'> + </div> + </div> +</div> + +<script> +var element = document.getElementById('element'); +var container = document.getElementById('container'); + +test(function() { + element.style.fontSize = '10px'; + var player = element.animate([{width: '3em'}, {width: '5em'}], 10); + player.pause(); + player.currentTime = 5; + element.style.fontSize = '20px'; + assert_equals(getComputedStyle(element).width, '80px'); +}, 'width responsive to style changes'); + +test(function() { + element.style.fontSize = '10px'; + var player = element.animate([{width: '40px'}, {width: 'calc(40px - 2em)'}], 10); + player.pause(); + + player.currentTime = 5; + element.style.fontSize = '40px'; + assert_equals(getComputedStyle(element).width, '20px'); + + player.currentTime = 7.5; + assert_equals(getComputedStyle(element).width, '10px'); +}, 'width clamped to 0px on keyframes'); + +test(function() { + container.style.width = 'auto'; + var player = element.animate([{width: 'inherit'}, {width: '20px'}], 4000); + player.pause(); + + player.currentTime = 1000; + assert_equals(getComputedStyle(element).width, '500px'); + + container.style.width = '100px'; + assert_equals(getComputedStyle(element).width, '80px'); + + container.style.width = 'min-content'; + assert_equals(getComputedStyle(element).width, '20px'); + + container.style.width = '100px'; + assert_equals(getComputedStyle(element).width, '80px'); + + container.style.width = 'max-content'; + assert_equals(getComputedStyle(element).width, '20px'); + + container.style.width = '100px'; + assert_equals(getComputedStyle(element).width, '80px'); + + container.style.width = 'fit-content'; + assert_equals(getComputedStyle(element).width, '20px'); +}, 'width responsive to inherited changes from keyword'); + +test(function() { + container.style.fontSize = '10px'; + container.style.width = '100px'; + var player = element.animate([{width: 'inherit'}, {width: '200px'}], 4000); + player.pause(); + + player.currentTime = 1000; + assert_equals(getComputedStyle(element).width, '125px'); + + container.style.width = '120px'; + assert_equals(getComputedStyle(element).width, '140px'); + + container.style.width = '100px'; + assert_equals(getComputedStyle(element).width, '125px'); +}, 'width responsive to inherited changes from length'); + +</script> |