summaryrefslogtreecommitdiffstats
path: root/devtools/client/fronts/animation.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/fronts/animation.js')
-rw-r--r--devtools/client/fronts/animation.js214
1 files changed, 214 insertions, 0 deletions
diff --git a/devtools/client/fronts/animation.js b/devtools/client/fronts/animation.js
new file mode 100644
index 0000000000..7dd17fc6e5
--- /dev/null
+++ b/devtools/client/fronts/animation.js
@@ -0,0 +1,214 @@
+/* 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("resource://devtools/shared/protocol.js");
+const {
+ animationPlayerSpec,
+ animationsSpec,
+} = require("resource://devtools/shared/specs/animation.js");
+
+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),
+ properties: this._form.properties,
+ };
+ }
+
+ /**
+ * 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);