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.
If target server dones't support created current time, this patch will use the
start time. As result of it, scrubber position will overlap to animation name
area if current time is negative.
MozReview-Commit-ID: 3PoBcHvaTgH
--- 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/utils/timescale.js
+++ b/devtools/client/inspector/animation/utils/timescale.js
@@ -21,30 +21,39 @@ 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,
+ createdCurrentTime,
currentTime,
delay,
duration,
endDelay = 0,
iterationCount,
playbackRate,
} = animation.state;
const toRate = v => v / playbackRate;
- const startTime = createdTime + toRate(Math.min(delay, 0));
+ let startTime = createdTime + toRate(Math.min(delay, 0));
+ const startPositionTime =
+ createdTime + toRate(createdCurrentTime ? createdCurrentTime : startTime);
+ // The created current time shorter than start time, this mean this animation
+ // doesn't start the animation due to other animation factors.
+ if (startPositionTime < startTime) {
+ startTime = startPositionTime;
+ }
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.
@@ -55,21 +64,23 @@ class TimeScale {
duration * (iterationCount || 1) +
Math.max(endDelay, 0));
}
minStartTime = Math.min(minStartTime, startTime);
maxEndTime = Math.max(maxEndTime, endTime);
animationsCurrentTime =
Math.max(animationsCurrentTime, createdTime + toRate(currentTime));
+ zeroPositionTime = Math.max(zeroPositionTime, (createdTime - startTime));
}
this.minStartTime = minStartTime;
this.maxEndTime = maxEndTime;
this.currentTime = animationsCurrentTime;
+ this.zeroPositionTime = 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
*/
@@ -106,49 +117,47 @@ class TimeScale {
relevantDelay +
Math.min(startTime, 0)
);
const length = toRate(delay) + rateRelativeDuration + toRate(minZero(endDelay));
const endTime = previousStartTime + length;
this.maxEndTime = Math.max(this.maxEndTime, endTime);
this.documentCurrentTime = Math.max(this.documentCurrentTime, documentCurrentTime);
+ this.zeroPositionTime = this.minStartTime;
}
}
/**
- * 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));
}
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -83,16 +83,17 @@ var AnimationPlayerActor = protocol.Acto
if (this.isPseudoElement) {
this.observer.observe(this.node.parentElement,
{animations: true, subtree: true});
} else {
this.observer.observe(this.node, {animations: true});
}
this.createdTime = createdTime;
+ this.createdCurrentTime = player.currentTime;
},
destroy: function() {
// Only try to disconnect the observer if it's not already dead (i.e. if the
// container view hasn't navigated since).
if (this.observer && !Cu.isDeadWrapper(this.observer)) {
this.observer.disconnect();
}
@@ -331,16 +332,18 @@ var AnimationPlayerActor = protocol.Acto
.some(propState => propState.runningOnCompositor),
propertyState: this.getPropertiesCompositorStatus(),
// The document timeline's currentTime is being sent along too. This is
// not strictly related to the node's animationPlayer, but is useful to
// know the current time of the animation with respect to the document's.
documentCurrentTime: this.node.ownerDocument.timeline.currentTime,
// The time which this animation created.
createdTime: this.createdTime,
+ // The time which an animation's current time when this animation has created.
+ createdCurrentTime: this.createdCurrentTime,
};
},
/**
* Get the current state of the AnimationPlayer (currentTime, playState, ...).
* Note that the initial state is returned as the form of this actor when it
* is initialized.
* This protocol method only returns a trimed down version of this state in
--- a/devtools/shared/fronts/animation.js
+++ b/devtools/shared/fronts/animation.js
@@ -67,16 +67,17 @@ const AnimationPlayerFront = FrontClassW
easing: this._form.easing,
fill: this._form.fill,
direction: this._form.direction,
animationTimingFunction: this._form.animationTimingFunction,
isRunningOnCompositor: this._form.isRunningOnCompositor,
propertyState: this._form.propertyState,
documentCurrentTime: this._form.documentCurrentTime,
createdTime: this._form.createdTime,
+ createdCurrentTime: this._form.createdCurrentTime,
};
},
/**
* Executed when the AnimationPlayerActor emits a "changed" event. Used to
* update the local knowledge of the state.
*/
onChanged: preEvent("changed", function(partialState) {