Bug 1622408 [wpt PR 22244] - Restore the event delegate for a CSSTransition after...
[gecko.git] / devtools / shared / fronts / animation.js
blob4bd452a1cba43b870e23a42ab7de61e98e102400
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const {
8   FrontClassWithSpec,
9   registerFront,
10 } = require("devtools/shared/protocol");
11 const {
12   animationPlayerSpec,
13   animationsSpec,
14 } = require("devtools/shared/specs/animation");
16 class AnimationPlayerFront extends FrontClassWithSpec(animationPlayerSpec) {
17   constructor(conn, targetFront, parentFront) {
18     super(conn, targetFront, parentFront);
20     this.state = {};
21     this.before("changed", this.onChanged.bind(this));
22   }
24   form(form) {
25     this._form = form;
26     this.state = this.initialState;
27   }
29   /**
30    * If the AnimationsActor was given a reference to the WalkerActor previously
31    * then calling this getter will return the animation target NodeFront.
32    */
33   get animationTargetNodeFront() {
34     if (!this._form.animationTargetNodeActorID) {
35       return null;
36     }
38     return this.conn.getFrontByID(this._form.animationTargetNodeActorID);
39   }
41   /**
42    * Getter for the initial state of the player. Up to date states can be
43    * retrieved by calling the getCurrentState method.
44    */
45   get initialState() {
46     return {
47       type: this._form.type,
48       startTime: this._form.startTime,
49       currentTime: this._form.currentTime,
50       playState: this._form.playState,
51       playbackRate: this._form.playbackRate,
52       name: this._form.name,
53       duration: this._form.duration,
54       delay: this._form.delay,
55       endDelay: this._form.endDelay,
56       iterationCount: this._form.iterationCount,
57       iterationStart: this._form.iterationStart,
58       easing: this._form.easing,
59       fill: this._form.fill,
60       direction: this._form.direction,
61       animationTimingFunction: this._form.animationTimingFunction,
62       isRunningOnCompositor: this._form.isRunningOnCompositor,
63       propertyState: this._form.propertyState,
64       documentCurrentTime: this._form.documentCurrentTime,
65       createdTime: this._form.createdTime,
66       currentTimeAtCreated: this._form.currentTimeAtCreated,
67       absoluteValues: this.calculateAbsoluteValues(this._form),
68     };
69   }
71   /**
72    * Executed when the AnimationPlayerActor emits a "changed" event. Used to
73    * update the local knowledge of the state.
74    */
75   onChanged(partialState) {
76     const { state } = this.reconstructState(partialState);
77     this.state = state;
78   }
80   /**
81    * Refresh the current state of this animation on the client from information
82    * found on the server. Doesn't return anything, just stores the new state.
83    */
84   async refreshState() {
85     const data = await this.getCurrentState();
86     if (this.currentStateHasChanged) {
87       this.state = data;
88     }
89   }
91   /**
92    * getCurrentState interceptor re-constructs incomplete states since the actor
93    * only sends the values that have changed.
94    */
95   getCurrentState() {
96     this.currentStateHasChanged = false;
97     return super.getCurrentState().then(partialData => {
98       const { state, hasChanged } = this.reconstructState(partialData);
99       this.currentStateHasChanged = hasChanged;
100       return state;
101     });
102   }
104   reconstructState(data) {
105     let hasChanged = false;
107     for (const key in this.state) {
108       if (typeof data[key] === "undefined") {
109         data[key] = this.state[key];
110       } else if (data[key] !== this.state[key]) {
111         hasChanged = true;
112       }
113     }
115     data.absoluteValues = this.calculateAbsoluteValues(data);
116     return { state: data, hasChanged };
117   }
119   calculateAbsoluteValues(data) {
120     const {
121       createdTime,
122       currentTime,
123       currentTimeAtCreated,
124       delay,
125       duration,
126       endDelay = 0,
127       fill,
128       iterationCount,
129       playbackRate,
130     } = data;
132     const toRate = v => v / Math.abs(playbackRate);
133     const isPositivePlaybackRate = playbackRate > 0;
134     let absoluteDelay = 0;
135     let absoluteEndDelay = 0;
136     let isDelayFilled = false;
137     let isEndDelayFilled = false;
139     if (isPositivePlaybackRate) {
140       absoluteDelay = toRate(delay);
141       absoluteEndDelay = toRate(endDelay);
142       isDelayFilled = fill === "both" || fill === "backwards";
143       isEndDelayFilled = fill === "both" || fill === "forwards";
144     } else {
145       absoluteDelay = toRate(endDelay);
146       absoluteEndDelay = toRate(delay);
147       isDelayFilled = fill === "both" || fill === "forwards";
148       isEndDelayFilled = fill === "both" || fill === "backwards";
149     }
151     let endTime = 0;
153     if (duration === Infinity) {
154       // Set endTime so as to enable the scrubber with keeping the consinstency of UI
155       // even the duration was Infinity. In case of delay is longer than zero, handle
156       // the graph duration as double of the delay amount. In case of no delay, handle
157       // the duration as 1ms which is short enough so as to make the scrubber movable
158       // and the limited duration is prioritized.
159       endTime = absoluteDelay > 0 ? absoluteDelay * 2 : 1;
160     } else {
161       endTime =
162         absoluteDelay +
163         toRate(duration * (iterationCount || 1)) +
164         absoluteEndDelay;
165     }
167     const absoluteCreatedTime = isPositivePlaybackRate
168       ? createdTime
169       : createdTime - endTime;
170     const absoluteCurrentTimeAtCreated = isPositivePlaybackRate
171       ? currentTimeAtCreated
172       : endTime - currentTimeAtCreated;
173     const animationCurrentTime = isPositivePlaybackRate
174       ? currentTime
175       : endTime - currentTime;
176     const absoluteCurrentTime =
177       absoluteCreatedTime + toRate(animationCurrentTime);
178     const absoluteStartTime = absoluteCreatedTime + Math.min(absoluteDelay, 0);
179     const absoluteStartTimeAtCreated =
180       absoluteCreatedTime + absoluteCurrentTimeAtCreated;
181     // To show whole graph with endDelay, we add negative endDelay amount to endTime.
182     const endTimeWithNegativeEndDelay = endTime - Math.min(absoluteEndDelay, 0);
183     const absoluteEndTime = absoluteCreatedTime + endTimeWithNegativeEndDelay;
185     return {
186       createdTime: absoluteCreatedTime,
187       currentTime: absoluteCurrentTime,
188       currentTimeAtCreated: absoluteCurrentTimeAtCreated,
189       delay: absoluteDelay,
190       endDelay: absoluteEndDelay,
191       endTime: absoluteEndTime,
192       isDelayFilled,
193       isEndDelayFilled,
194       startTime: absoluteStartTime,
195       startTimeAtCreated: absoluteStartTimeAtCreated,
196     };
197   }
200 exports.AnimationPlayerFront = AnimationPlayerFront;
201 registerFront(AnimationPlayerFront);
203 class AnimationsFront extends FrontClassWithSpec(animationsSpec) {
204   constructor(client, targetFront, parentFront) {
205     super(client, targetFront, parentFront);
207     // Attribute name from which to retrieve the actorID out of the target actor's form
208     this.formAttributeName = "animationsActor";
209   }
212 exports.AnimationsFront = AnimationsFront;
213 registerFront(AnimationsFront);