animation.js (7017B)
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/. */ 4 5 "use strict"; 6 7 const { 8 FrontClassWithSpec, 9 registerFront, 10 } = require("resource://devtools/shared/protocol.js"); 11 const { 12 animationPlayerSpec, 13 animationsSpec, 14 } = require("resource://devtools/shared/specs/animation.js"); 15 16 class AnimationPlayerFront extends FrontClassWithSpec(animationPlayerSpec) { 17 constructor(conn, targetFront, parentFront) { 18 super(conn, targetFront, parentFront); 19 20 this.state = {}; 21 this.before("changed", this.onChanged.bind(this)); 22 } 23 24 form(form) { 25 this._form = form; 26 this.state = this.initialState; 27 } 28 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 } 37 38 return this.conn.getFrontByID(this._form.animationTargetNodeActorID); 39 } 40 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 properties: this._form.properties, 69 }; 70 } 71 72 /** 73 * Executed when the AnimationPlayerActor emits a "changed" event. Used to 74 * update the local knowledge of the state. 75 */ 76 onChanged(partialState) { 77 const { state } = this.reconstructState(partialState); 78 this.state = state; 79 } 80 81 /** 82 * Refresh the current state of this animation on the client from information 83 * found on the server. Doesn't return anything, just stores the new state. 84 */ 85 async refreshState() { 86 const data = await this.getCurrentState(); 87 if (this.currentStateHasChanged) { 88 this.state = data; 89 } 90 } 91 92 /** 93 * getCurrentState interceptor re-constructs incomplete states since the actor 94 * only sends the values that have changed. 95 */ 96 getCurrentState() { 97 this.currentStateHasChanged = false; 98 return super.getCurrentState().then(partialData => { 99 const { state, hasChanged } = this.reconstructState(partialData); 100 this.currentStateHasChanged = hasChanged; 101 return state; 102 }); 103 } 104 105 reconstructState(data) { 106 let hasChanged = false; 107 108 for (const key in this.state) { 109 if (typeof data[key] === "undefined") { 110 data[key] = this.state[key]; 111 } else if (data[key] !== this.state[key]) { 112 hasChanged = true; 113 } 114 } 115 116 data.absoluteValues = this.calculateAbsoluteValues(data); 117 return { state: data, hasChanged }; 118 } 119 120 calculateAbsoluteValues(data) { 121 const { 122 createdTime, 123 currentTime, 124 currentTimeAtCreated, 125 delay, 126 duration, 127 endDelay = 0, 128 fill, 129 iterationCount, 130 playbackRate, 131 } = data; 132 133 const toRate = v => v / Math.abs(playbackRate); 134 const isPositivePlaybackRate = playbackRate > 0; 135 let absoluteDelay = 0; 136 let absoluteEndDelay = 0; 137 let isDelayFilled = false; 138 let isEndDelayFilled = false; 139 140 if (isPositivePlaybackRate) { 141 absoluteDelay = toRate(delay); 142 absoluteEndDelay = toRate(endDelay); 143 isDelayFilled = fill === "both" || fill === "backwards"; 144 isEndDelayFilled = fill === "both" || fill === "forwards"; 145 } else { 146 absoluteDelay = toRate(endDelay); 147 absoluteEndDelay = toRate(delay); 148 isDelayFilled = fill === "both" || fill === "forwards"; 149 isEndDelayFilled = fill === "both" || fill === "backwards"; 150 } 151 152 let endTime = 0; 153 154 if (duration === Infinity) { 155 // Set endTime so as to enable the scrubber with keeping the consinstency of UI 156 // even the duration was Infinity. In case of delay is longer than zero, handle 157 // the graph duration as double of the delay amount. In case of no delay, handle 158 // the duration as 1ms which is short enough so as to make the scrubber movable 159 // and the limited duration is prioritized. 160 endTime = absoluteDelay > 0 ? absoluteDelay * 2 : 1; 161 } else { 162 endTime = 163 absoluteDelay + 164 toRate(duration * (iterationCount || 1)) + 165 absoluteEndDelay; 166 } 167 168 const absoluteCreatedTime = isPositivePlaybackRate 169 ? createdTime 170 : createdTime - endTime; 171 const absoluteCurrentTimeAtCreated = isPositivePlaybackRate 172 ? currentTimeAtCreated 173 : endTime - currentTimeAtCreated; 174 const animationCurrentTime = isPositivePlaybackRate 175 ? currentTime 176 : endTime - currentTime; 177 const absoluteCurrentTime = 178 absoluteCreatedTime + toRate(animationCurrentTime); 179 const absoluteStartTime = absoluteCreatedTime + Math.min(absoluteDelay, 0); 180 const absoluteStartTimeAtCreated = 181 absoluteCreatedTime + absoluteCurrentTimeAtCreated; 182 // To show whole graph with endDelay, we add negative endDelay amount to endTime. 183 const endTimeWithNegativeEndDelay = endTime - Math.min(absoluteEndDelay, 0); 184 const absoluteEndTime = absoluteCreatedTime + endTimeWithNegativeEndDelay; 185 186 return { 187 createdTime: absoluteCreatedTime, 188 currentTime: absoluteCurrentTime, 189 currentTimeAtCreated: absoluteCurrentTimeAtCreated, 190 delay: absoluteDelay, 191 endDelay: absoluteEndDelay, 192 endTime: absoluteEndTime, 193 isDelayFilled, 194 isEndDelayFilled, 195 startTime: absoluteStartTime, 196 startTimeAtCreated: absoluteStartTimeAtCreated, 197 }; 198 } 199 } 200 201 registerFront(AnimationPlayerFront); 202 203 class AnimationsFront extends FrontClassWithSpec(animationsSpec) { 204 constructor(client, targetFront, parentFront) { 205 super(client, targetFront, parentFront); 206 207 // Attribute name from which to retrieve the actorID out of the target actor's form 208 this.formAttributeName = "animationsActor"; 209 } 210 211 setWalkerActor(walkerFront) { 212 this.walker = walkerFront; 213 return super.setWalkerActor(walkerFront); 214 } 215 216 destroy() { 217 super.destroy(); 218 this.walker = null; 219 } 220 } 221 222 registerFront(AnimationsFront);