gecko-dev/devtools/shared/fronts/animation.js

214 строки
6.7 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
FrontClassWithSpec,
registerFront,
} = require("devtools/shared/protocol");
const {
animationPlayerSpec,
animationsSpec,
} = require("devtools/shared/specs/animation");
class AnimationPlayerFront extends FrontClassWithSpec(animationPlayerSpec) {
constructor(conn, targetFront, parentFront) {
super(conn, targetFront, parentFront);
this.state = {};
this.before("changed", this.onChanged.bind(this));
}
form(form) {
this._form = form;
this.state = this.initialState;
}
/**
* If the AnimationsActor was given a reference to the WalkerActor previously
* then calling this getter will return the animation target NodeFront.
*/
get animationTargetNodeFront() {
if (!this._form.animationTargetNodeActorID) {
return null;
}
return this.conn.getFrontByID(this._form.animationTargetNodeActorID);
}
/**
* Getter for the initial state of the player. Up to date states can be
* retrieved by calling the getCurrentState method.
*/
get initialState() {
return {
type: this._form.type,
startTime: this._form.startTime,
currentTime: this._form.currentTime,
playState: this._form.playState,
playbackRate: this._form.playbackRate,
name: this._form.name,
duration: this._form.duration,
delay: this._form.delay,
endDelay: this._form.endDelay,
iterationCount: this._form.iterationCount,
iterationStart: this._form.iterationStart,
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,
currentTimeAtCreated: this._form.currentTimeAtCreated,
absoluteValues: this.calculateAbsoluteValues(this._form),
};
}
/**
* Executed when the AnimationPlayerActor emits a "changed" event. Used to
* update the local knowledge of the state.
*/
onChanged(partialState) {
const { state } = this.reconstructState(partialState);
this.state = state;
}
/**
* Refresh the current state of this animation on the client from information
* found on the server. Doesn't return anything, just stores the new state.
*/
async refreshState() {
const data = await this.getCurrentState();
if (this.currentStateHasChanged) {
this.state = data;
}
}
/**
* getCurrentState interceptor re-constructs incomplete states since the actor
* only sends the values that have changed.
*/
getCurrentState() {
this.currentStateHasChanged = false;
return super.getCurrentState().then(partialData => {
const { state, hasChanged } = this.reconstructState(partialData);
this.currentStateHasChanged = hasChanged;
return state;
});
}
reconstructState(data) {
let hasChanged = false;
for (const key in this.state) {
if (typeof data[key] === "undefined") {
data[key] = this.state[key];
} else if (data[key] !== this.state[key]) {
hasChanged = true;
}
}
data.absoluteValues = this.calculateAbsoluteValues(data);
return { state: data, hasChanged };
}
calculateAbsoluteValues(data) {
const {
createdTime,
currentTime,
currentTimeAtCreated,
delay,
duration,
endDelay = 0,
fill,
iterationCount,
playbackRate,
} = data;
const toRate = v => v / Math.abs(playbackRate);
const isPositivePlaybackRate = playbackRate > 0;
let absoluteDelay = 0;
let absoluteEndDelay = 0;
let isDelayFilled = false;
let isEndDelayFilled = false;
if (isPositivePlaybackRate) {
absoluteDelay = toRate(delay);
absoluteEndDelay = toRate(endDelay);
isDelayFilled = fill === "both" || fill === "backwards";
isEndDelayFilled = fill === "both" || fill === "forwards";
} else {
absoluteDelay = toRate(endDelay);
absoluteEndDelay = toRate(delay);
isDelayFilled = fill === "both" || fill === "forwards";
isEndDelayFilled = fill === "both" || fill === "backwards";
}
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.
endTime = absoluteDelay > 0 ? absoluteDelay * 2 : 1;
} else {
endTime =
absoluteDelay +
toRate(duration * (iterationCount || 1)) +
absoluteEndDelay;
}
const absoluteCreatedTime = isPositivePlaybackRate
? createdTime
: createdTime - endTime;
const absoluteCurrentTimeAtCreated = isPositivePlaybackRate
? currentTimeAtCreated
: endTime - currentTimeAtCreated;
const animationCurrentTime = isPositivePlaybackRate
? currentTime
: endTime - currentTime;
const absoluteCurrentTime =
absoluteCreatedTime + toRate(animationCurrentTime);
const absoluteStartTime = absoluteCreatedTime + Math.min(absoluteDelay, 0);
const absoluteStartTimeAtCreated =
absoluteCreatedTime + absoluteCurrentTimeAtCreated;
// To show whole graph with endDelay, we add negative endDelay amount to endTime.
const endTimeWithNegativeEndDelay = endTime - Math.min(absoluteEndDelay, 0);
const absoluteEndTime = absoluteCreatedTime + endTimeWithNegativeEndDelay;
return {
createdTime: absoluteCreatedTime,
currentTime: absoluteCurrentTime,
currentTimeAtCreated: absoluteCurrentTimeAtCreated,
delay: absoluteDelay,
endDelay: absoluteEndDelay,
endTime: absoluteEndTime,
isDelayFilled,
isEndDelayFilled,
startTime: absoluteStartTime,
startTimeAtCreated: absoluteStartTimeAtCreated,
};
}
}
exports.AnimationPlayerFront = AnimationPlayerFront;
registerFront(AnimationPlayerFront);
class AnimationsFront extends FrontClassWithSpec(animationsSpec) {
constructor(client, targetFront, parentFront) {
super(client, targetFront, parentFront);
// Attribute name from which to retrieve the actorID out of the target actor's form
this.formAttributeName = "animationsActor";
}
}
exports.AnimationsFront = AnimationsFront;
registerFront(AnimationsFront);