diff options
Diffstat (limited to 'devtools/client/fronts/animation.js')
-rw-r--r-- | devtools/client/fronts/animation.js | 214 |
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); |