Bug 1468475 - Part 1. Introduce zeroPositionTime in order to shift the graduation if animations have negative-delay. r?daisuke
This patch will introduce the zeroPositionTime, this value means the time that
current time of animation will be zero. In front-side, use this value for
shifting the graduation in order to display the zero.
MozReview-Commit-ID: I7avAdBPS4a
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -372,17 +372,18 @@ class AnimationInspector {
}
removeAnimationsCurrentTimeListener(listener) {
this.animationsCurrentTimeListeners =
this.animationsCurrentTimeListeners.filter(l => l !== listener);
}
async rewindAnimationsCurrentTime() {
- await this.setAnimationsCurrentTime(0, true);
+ const { timeScale } = this.state;
+ await this.setAnimationsCurrentTime(timeScale.zeroPositionTime, true);
}
selectAnimation(animation) {
this.inspector.store.dispatch(updateSelectedAnimation(animation));
}
async setSelectedNode(nodeFront) {
if (this.inspector.selection.nodeFront === nodeFront) {
@@ -450,17 +451,17 @@ class AnimationInspector {
await this.inspector.target.actorHasMethod("animations", "pauseSome");
}
let { animations, timeScale } = this.state;
try {
if (doPlay && animations.every(animation =>
timeScale.getEndTime(animation) <= animation.state.currentTime)) {
- await this.doSetCurrentTimes(0);
+ await this.doSetCurrentTimes(timeScale.zeroPositionTime);
}
// If the server does not support pauseSome/playSome function, (which happens
// when connected to server older than FF62), use pauseAll/playAll instead.
// See bug 1456857.
if (this.hasPausePlaySome) {
if (doPlay) {
await this.animationsFront.playSome(animations);
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -38,16 +38,17 @@ support-files =
[browser_animation_animation-target_highlight.js]
[browser_animation_animation-target_select.js]
[browser_animation_animation-timeline-tick.js]
[browser_animation_css-transition-with-playstate-idle.js]
[browser_animation_current-time-label.js]
[browser_animation_current-time-scrubber.js]
[browser_animation_current-time-scrubber-rtl.js]
[browser_animation_current-time-scrubber_each-different-creation-time-animations.js]
+[browser_animation_current-time-scrubber-with-negative-delay.js]
[browser_animation_empty_on_invalid_nodes.js]
[browser_animation_indication-bar.js]
[browser_animation_infinity-duration_current-time-scrubber.js]
[browser_animation_infinity-duration_summary-graph.js]
[browser_animation_infinity-duration_tick-label.js]
[browser_animation_inspector_exists.js]
[browser_animation_keyframes-graph_computed-value-path-01.js]
[browser_animation_keyframes-graph_computed-value-path-02.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_current-time-scrubber-with-negative-delay.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test whether the most left position means negative current time.
+
+add_task(async function() {
+ await addTab(URL_ROOT + "doc_multi_timings.html");
+ await removeAnimatedElementsExcept([".cssanimation-normal", ".delay-negative"]);
+ const { animationInspector, panel, inspector } = await openAnimationInspector();
+
+ info("Checking scrubber controller existence");
+ const controllerEl = panel.querySelector(".current-time-scrubber-area");
+ ok(controllerEl, "scrubber controller should exist");
+
+ info("Checking the current time of most left scrubber position");
+ const timeScale = animationInspector.state.timeScale;
+ await clickOnCurrentTimeScrubberController(animationInspector, panel, 0);
+ assertAnimationsCurrentTime(animationInspector, -1 * timeScale.zeroPositionTime);
+
+ info("Select negative current time animation");
+ await selectNodeAndWaitForAnimations(".cssanimation-normal", inspector);
+ assertAnimationsCurrentTime(animationInspector, -1 * timeScale.zeroPositionTime);
+
+ info("Back to 'body' and rewind the animation");
+ await selectNodeAndWaitForAnimations("body", inspector);
+ await clickOnRewindButton(animationInspector, panel);
+ assertAnimationsCurrentTime(animationInspector, 0);
+});
--- a/devtools/client/inspector/animation/test/browser_animation_pause-resume-button_end-time.js
+++ b/devtools/client/inspector/animation/test/browser_animation_pause-resume-button_end-time.js
@@ -3,38 +3,41 @@
"use strict";
// Test whether the animation can rewind if the current time is over end time when
// the resume button clicked.
add_task(async function() {
await addTab(URL_ROOT + "doc_simple_animation.html");
- await removeAnimatedElementsExcept([".animated", ".end-delay", ".long"]);
+ await removeAnimatedElementsExcept([".animated",
+ ".end-delay",
+ ".long",
+ ".negative-delay"]);
const { animationInspector, panel } = await openAnimationInspector();
info("Check animations state after resuming with infinite animation");
info("Make the current time of animation to be over its end time");
await clickOnCurrentTimeScrubberController(animationInspector, panel, 1);
info("Resume animations");
await clickOnPauseResumeButton(animationInspector, panel);
await wait(1000);
assertPlayState(animationInspector.state.animations,
- ["running", "finished", "finished"]);
+ ["running", "finished", "finished", "finished"]);
await clickOnCurrentTimeScrubberController(animationInspector, panel, 0);
info("Check animations state after resuming without infinite animation");
info("Remove infinite animation");
await setClassAttribute(animationInspector, ".animated", "ball still");
info("Make the current time of animation to be over its end time");
await clickOnCurrentTimeScrubberController(animationInspector, panel, 1);
await clickOnPlaybackRateSelector(animationInspector, panel, 10);
info("Resume animations");
await clickOnPauseResumeButton(animationInspector, panel);
- assertPlayState(animationInspector.state.animations, ["running", "running"]);
+ assertPlayState(animationInspector.state.animations, ["running", "running", "running"]);
assertCurrentTimeLessThanDuration(animationInspector.state.animations);
assertScrubberPosition(panel);
});
function assertPlayState(animations, expectedState) {
animations.forEach((animation, index) => {
is(animation.state.playState, expectedState[index],
`The playState of animation [${ index }] should be ${ expectedState[index] }`);
--- a/devtools/client/inspector/animation/test/browser_animation_rewind-button.js
+++ b/devtools/client/inspector/animation/test/browser_animation_rewind-button.js
@@ -4,17 +4,17 @@
"use strict";
// Test for following RewindButton component:
// * element existence
// * make animations to rewind to zero
// * the state should be always paused after rewinding
add_task(async function() {
- await addTab(URL_ROOT + "doc_custom_playback_rate.html");
+ await addTab(URL_ROOT + "doc_multi_timings.html");
const { animationInspector, panel } = await openAnimationInspector();
info("Checking button existence");
ok(panel.querySelector(".rewind-button"), "Rewind button should exist");
info("Checking rewind button makes animations to rewind to zero");
await clickOnRewindButton(animationInspector, panel);
assertAnimationsCurrentTime(animationInspector, 0);
--- a/devtools/client/inspector/animation/utils/timescale.js
+++ b/devtools/client/inspector/animation/utils/timescale.js
@@ -21,30 +21,37 @@ class TimeScale {
if (!animations.every(animation => animation.state.createdTime)) {
// Backward compatibility for createdTime.
return this._initializeWithoutCreatedTime(animations);
}
let animationsCurrentTime = -Number.MAX_VALUE;
let minStartTime = Infinity;
let maxEndTime = 0;
+ let zeroPositionTime = 0;
for (const animation of animations) {
const {
createdTime,
currentTime,
delay,
duration,
endDelay = 0,
iterationCount,
playbackRate,
} = animation.state;
const toRate = v => v / playbackRate;
- const startTime = createdTime + toRate(Math.min(delay, 0));
+ const animationCurrentTime = createdTime + toRate(currentTime);
+ let startTime = createdTime + toRate(Math.min(delay, 0));
+ // The current time shorter than start time, this mean this animation doesn't
+ // start the animation due to other negative-delay.
+ if (animationCurrentTime < startTime) {
+ startTime = animationCurrentTime;
+ }
let endTime = 0;
if (duration === Infinity) {
// Set endTime so as to enable the scrubber with keeping the consinstency of UI
// even the duration was Infinity. In case of delay is longer than zero, handle
// the graph duration as double of the delay amount. In case of no delay, handle
// the duration as 1ms which is short enough so as to make the scrubber movable
// and the limited duration is prioritized.
@@ -54,22 +61,24 @@ class TimeScale {
toRate(delay +
duration * (iterationCount || 1) +
Math.max(endDelay, 0));
}
minStartTime = Math.min(minStartTime, startTime);
maxEndTime = Math.max(maxEndTime, endTime);
animationsCurrentTime =
- Math.max(animationsCurrentTime, createdTime + toRate(currentTime));
+ Math.max(animationsCurrentTime, animationCurrentTime);
+ zeroPositionTime = Math.min(zeroPositionTime, (startTime - createdTime));
}
this.minStartTime = minStartTime;
this.maxEndTime = maxEndTime;
this.currentTime = animationsCurrentTime;
+ this.zeroPositionTime = -1 * zeroPositionTime;
}
/**
* Same as the constructor but doesn't use the animation's createdTime property
* which has only been added in FF62, for backward compatbility reasons.
*
* @param {Array} animations
*/
@@ -110,45 +119,42 @@ class TimeScale {
const endTime = previousStartTime + length;
this.maxEndTime = Math.max(this.maxEndTime, endTime);
this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
}
}
/**
- * Convert a distance in % to a time, in the current time scale.
- *
- * @param {Number} distance
- * @return {Number}
- */
- distanceToTime(distance) {
- return this.minStartTime + (this.getDuration() * distance / 100);
- }
-
- /**
- * Convert a distance in % to a time, in the current time scale.
- * The time will be relative to the current minimum start time.
+ * Convert a distance in % to a time, in the current time scale. The time
+ * will be relative to the zero position time.
+ * i.e., If zeroPositionTime will be negative and specified time is shorter
+ * than the absolute value of zero position time, relative time will be
+ * negative time.
*
* @param {Number} distance
* @return {Number}
*/
distanceToRelativeTime(distance) {
- const time = this.distanceToTime(distance);
- return time - this.minStartTime;
+ return (this.getDuration() * distance / 100)
+ - this.zeroPositionTime;
}
/**
* Depending on the time scale, format the given time as milliseconds or
* seconds.
*
* @param {Number} time
* @return {String} The formatted time string.
*/
formatTime(time) {
+ // Ignore negative zero
+ if (Math.abs(time) < (1 / 1000)) {
+ time = 0.0;
+ }
// Format in milliseconds if the total duration is short enough.
if (this.getDuration() <= TIME_FORMAT_MAX_DURATION_IN_MS) {
return getFormatStr("timeline.timeGraduationLabel", time.toFixed(0));
}
// Otherwise format in seconds.
return getFormatStr("player.timeLabel", (time / 1000).toFixed(1));
}