/src/mozilla-central/dom/animation/KeyframeEffect.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
5 | | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/dom/KeyframeEffect.h" |
8 | | |
9 | | #include "FrameLayerBuilder.h" |
10 | | #include "mozilla/dom/Animation.h" |
11 | | #include "mozilla/dom/KeyframeAnimationOptionsBinding.h" |
12 | | // For UnrestrictedDoubleOrKeyframeAnimationOptions; |
13 | | #include "mozilla/dom/CSSPseudoElement.h" |
14 | | #include "mozilla/dom/KeyframeEffectBinding.h" |
15 | | #include "mozilla/AnimationUtils.h" |
16 | | #include "mozilla/AutoRestore.h" |
17 | | #include "mozilla/ComputedStyleInlines.h" |
18 | | #include "mozilla/EffectSet.h" |
19 | | #include "mozilla/FloatingPoint.h" // For IsFinite |
20 | | #include "mozilla/LayerAnimationInfo.h" |
21 | | #include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt |
22 | | #include "mozilla/KeyframeUtils.h" |
23 | | #include "mozilla/ServoBindings.h" |
24 | | #include "mozilla/StaticPrefs.h" |
25 | | #include "mozilla/TypeTraits.h" |
26 | | #include "Layers.h" // For Layer |
27 | | #include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetComputedStyle |
28 | | #include "nsContentUtils.h" |
29 | | #include "nsCSSPropertyIDSet.h" |
30 | | #include "nsCSSProps.h" // For nsCSSProps::PropHasFlags |
31 | | #include "nsCSSPseudoElements.h" // For CSSPseudoElementType |
32 | | #include "nsIFrame.h" |
33 | | #include "nsIPresShell.h" |
34 | | #include "nsIScriptError.h" |
35 | | #include "nsRefreshDriver.h" |
36 | | |
37 | | namespace mozilla { |
38 | | |
39 | | bool |
40 | | PropertyValuePair::operator==(const PropertyValuePair& aOther) const |
41 | 0 | { |
42 | 0 | if (mProperty != aOther.mProperty) { |
43 | 0 | return false; |
44 | 0 | } |
45 | 0 | if (mServoDeclarationBlock == aOther.mServoDeclarationBlock) { |
46 | 0 | return true; |
47 | 0 | } |
48 | 0 | if (!mServoDeclarationBlock || !aOther.mServoDeclarationBlock) { |
49 | 0 | return false; |
50 | 0 | } |
51 | 0 | return Servo_DeclarationBlock_Equals(mServoDeclarationBlock, |
52 | 0 | aOther.mServoDeclarationBlock); |
53 | 0 | } |
54 | | |
55 | | namespace dom { |
56 | | |
57 | | NS_IMPL_CYCLE_COLLECTION_INHERITED(KeyframeEffect, |
58 | | AnimationEffect, |
59 | | mTarget) |
60 | | |
61 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(KeyframeEffect, |
62 | 0 | AnimationEffect) |
63 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
64 | | |
65 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(KeyframeEffect) |
66 | 0 | NS_INTERFACE_MAP_END_INHERITING(AnimationEffect) |
67 | | |
68 | | NS_IMPL_ADDREF_INHERITED(KeyframeEffect, AnimationEffect) |
69 | | NS_IMPL_RELEASE_INHERITED(KeyframeEffect, AnimationEffect) |
70 | | |
71 | | KeyframeEffect::KeyframeEffect( |
72 | | nsIDocument* aDocument, |
73 | | const Maybe<OwningAnimationTarget>& aTarget, |
74 | | const TimingParams& aTiming, |
75 | | const KeyframeEffectParams& aOptions) |
76 | | : AnimationEffect(aDocument, aTiming) |
77 | | , mTarget(aTarget) |
78 | | , mEffectOptions(aOptions) |
79 | | , mInEffectOnLastAnimationTimingUpdate(false) |
80 | | , mCumulativeChangeHint(nsChangeHint(0)) |
81 | 0 | { |
82 | 0 | } |
83 | | |
84 | | JSObject* |
85 | | KeyframeEffect::WrapObject(JSContext* aCx, |
86 | | JS::Handle<JSObject*> aGivenProto) |
87 | 0 | { |
88 | 0 | return KeyframeEffect_Binding::Wrap(aCx, this, aGivenProto); |
89 | 0 | } |
90 | | |
91 | | IterationCompositeOperation KeyframeEffect::IterationComposite() const |
92 | 0 | { |
93 | 0 | return mEffectOptions.mIterationComposite; |
94 | 0 | } |
95 | | |
96 | | void |
97 | | KeyframeEffect::SetIterationComposite( |
98 | | const IterationCompositeOperation& aIterationComposite) |
99 | 0 | { |
100 | 0 | if (mEffectOptions.mIterationComposite == aIterationComposite) { |
101 | 0 | return; |
102 | 0 | } |
103 | 0 | |
104 | 0 | if (mAnimation && mAnimation->IsRelevant()) { |
105 | 0 | nsNodeUtils::AnimationChanged(mAnimation); |
106 | 0 | } |
107 | 0 |
|
108 | 0 | mEffectOptions.mIterationComposite = aIterationComposite; |
109 | 0 | RequestRestyle(EffectCompositor::RestyleType::Layer); |
110 | 0 | } |
111 | | |
112 | | CompositeOperation |
113 | | KeyframeEffect::Composite() const |
114 | 0 | { |
115 | 0 | return mEffectOptions.mComposite; |
116 | 0 | } |
117 | | |
118 | | void |
119 | | KeyframeEffect::SetComposite(const CompositeOperation& aComposite) |
120 | 0 | { |
121 | 0 | if (mEffectOptions.mComposite == aComposite) { |
122 | 0 | return; |
123 | 0 | } |
124 | 0 | |
125 | 0 | mEffectOptions.mComposite = aComposite; |
126 | 0 |
|
127 | 0 | if (mAnimation && mAnimation->IsRelevant()) { |
128 | 0 | nsNodeUtils::AnimationChanged(mAnimation); |
129 | 0 | } |
130 | 0 |
|
131 | 0 | if (mTarget) { |
132 | 0 | RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle(); |
133 | 0 | if (computedStyle) { |
134 | 0 | UpdateProperties(computedStyle); |
135 | 0 | } |
136 | 0 | } |
137 | 0 | } |
138 | | |
139 | | void |
140 | | KeyframeEffect::NotifySpecifiedTimingUpdated() |
141 | 0 | { |
142 | 0 | // Use the same document for a pseudo element and its parent element. |
143 | 0 | // Use nullptr if we don't have mTarget, so disable the mutation batch. |
144 | 0 | nsAutoAnimationMutationBatch mb(mTarget ? mTarget->mElement->OwnerDoc() |
145 | 0 | : nullptr); |
146 | 0 |
|
147 | 0 | if (mAnimation) { |
148 | 0 | mAnimation->NotifyEffectTimingUpdated(); |
149 | 0 |
|
150 | 0 | if (mAnimation->IsRelevant()) { |
151 | 0 | nsNodeUtils::AnimationChanged(mAnimation); |
152 | 0 | } |
153 | 0 |
|
154 | 0 | RequestRestyle(EffectCompositor::RestyleType::Layer); |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | void |
159 | | KeyframeEffect::NotifyAnimationTimingUpdated() |
160 | 0 | { |
161 | 0 | UpdateTargetRegistration(); |
162 | 0 |
|
163 | 0 | // If the effect is not relevant it will be removed from the target |
164 | 0 | // element's effect set. However, effects not in the effect set |
165 | 0 | // will not be included in the set of candidate effects for running on |
166 | 0 | // the compositor and hence they won't have their compositor status |
167 | 0 | // updated. As a result, we need to make sure we clear their compositor |
168 | 0 | // status here. |
169 | 0 | bool isRelevant = mAnimation && mAnimation->IsRelevant(); |
170 | 0 | if (!isRelevant) { |
171 | 0 | ResetIsRunningOnCompositor(); |
172 | 0 | } |
173 | 0 |
|
174 | 0 | // Request restyle if necessary. |
175 | 0 | if (mAnimation && !mProperties.IsEmpty() && HasComputedTimingChanged()) { |
176 | 0 | EffectCompositor::RestyleType restyleType = |
177 | 0 | CanThrottle() ? |
178 | 0 | EffectCompositor::RestyleType::Throttled : |
179 | 0 | EffectCompositor::RestyleType::Standard; |
180 | 0 | RequestRestyle(restyleType); |
181 | 0 | } |
182 | 0 |
|
183 | 0 | // Detect changes to "in effect" status since we need to recalculate the |
184 | 0 | // animation cascade for this element whenever that changes. |
185 | 0 | // Note that updating mInEffectOnLastAnimationTimingUpdate has to be done |
186 | 0 | // after above CanThrottle() call since the function uses the flag inside it. |
187 | 0 | bool inEffect = IsInEffect(); |
188 | 0 | if (inEffect != mInEffectOnLastAnimationTimingUpdate) { |
189 | 0 | MarkCascadeNeedsUpdate(); |
190 | 0 | mInEffectOnLastAnimationTimingUpdate = inEffect; |
191 | 0 | } |
192 | 0 |
|
193 | 0 | // If we're no longer "in effect", our ComposeStyle method will never be |
194 | 0 | // called and we will never have a chance to update mProgressOnLastCompose |
195 | 0 | // and mCurrentIterationOnLastCompose. |
196 | 0 | // We clear them here to ensure that if we later become "in effect" we will |
197 | 0 | // request a restyle (above). |
198 | 0 | if (!inEffect) { |
199 | 0 | mProgressOnLastCompose.SetNull(); |
200 | 0 | mCurrentIterationOnLastCompose = 0; |
201 | 0 | } |
202 | 0 | } |
203 | | |
204 | | static bool |
205 | | KeyframesEqualIgnoringComputedOffsets(const nsTArray<Keyframe>& aLhs, |
206 | | const nsTArray<Keyframe>& aRhs) |
207 | 0 | { |
208 | 0 | if (aLhs.Length() != aRhs.Length()) { |
209 | 0 | return false; |
210 | 0 | } |
211 | 0 | |
212 | 0 | for (size_t i = 0, len = aLhs.Length(); i < len; ++i) { |
213 | 0 | const Keyframe& a = aLhs[i]; |
214 | 0 | const Keyframe& b = aRhs[i]; |
215 | 0 | if (a.mOffset != b.mOffset || |
216 | 0 | a.mTimingFunction != b.mTimingFunction || |
217 | 0 | a.mPropertyValues != b.mPropertyValues) { |
218 | 0 | return false; |
219 | 0 | } |
220 | 0 | } |
221 | 0 | return true; |
222 | 0 | } |
223 | | |
224 | | // https://drafts.csswg.org/web-animations/#dom-keyframeeffect-setkeyframes |
225 | | void |
226 | | KeyframeEffect::SetKeyframes(JSContext* aContext, |
227 | | JS::Handle<JSObject*> aKeyframes, |
228 | | ErrorResult& aRv) |
229 | 0 | { |
230 | 0 | nsTArray<Keyframe> keyframes = |
231 | 0 | KeyframeUtils::GetKeyframesFromObject(aContext, mDocument, aKeyframes, aRv); |
232 | 0 | if (aRv.Failed()) { |
233 | 0 | return; |
234 | 0 | } |
235 | 0 | |
236 | 0 | RefPtr<ComputedStyle> style = GetTargetComputedStyle(); |
237 | 0 | SetKeyframes(std::move(keyframes), style); |
238 | 0 | } |
239 | | |
240 | | |
241 | | void |
242 | | KeyframeEffect::SetKeyframes( |
243 | | nsTArray<Keyframe>&& aKeyframes, |
244 | | const ComputedStyle* aStyle) |
245 | 0 | { |
246 | 0 | if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) { |
247 | 0 | return; |
248 | 0 | } |
249 | 0 | |
250 | 0 | mKeyframes = std::move(aKeyframes); |
251 | 0 | KeyframeUtils::DistributeKeyframes(mKeyframes); |
252 | 0 |
|
253 | 0 | if (mAnimation && mAnimation->IsRelevant()) { |
254 | 0 | nsNodeUtils::AnimationChanged(mAnimation); |
255 | 0 | } |
256 | 0 |
|
257 | 0 | // We need to call UpdateProperties() unless the target element doesn't have |
258 | 0 | // style (e.g. the target element is not associated with any document). |
259 | 0 | if (aStyle) { |
260 | 0 | UpdateProperties(aStyle); |
261 | 0 | MaybeUpdateFrameForCompositor(); |
262 | 0 | } |
263 | 0 | } |
264 | | |
265 | | const AnimationProperty* |
266 | | KeyframeEffect::GetEffectiveAnimationOfProperty(nsCSSPropertyID aProperty) const |
267 | 0 | { |
268 | 0 | EffectSet* effectSet = |
269 | 0 | EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); |
270 | 0 | for (size_t propIdx = 0, propEnd = mProperties.Length(); |
271 | 0 | propIdx != propEnd; ++propIdx) { |
272 | 0 | if (aProperty == mProperties[propIdx].mProperty) { |
273 | 0 | const AnimationProperty* result = &mProperties[propIdx]; |
274 | 0 | // Skip if there is a property of animation level that is overridden |
275 | 0 | // by !important rules. |
276 | 0 | if (effectSet && |
277 | 0 | effectSet->PropertiesWithImportantRules() |
278 | 0 | .HasProperty(result->mProperty) && |
279 | 0 | effectSet->PropertiesForAnimationsLevel() |
280 | 0 | .HasProperty(result->mProperty)) { |
281 | 0 | result = nullptr; |
282 | 0 | } |
283 | 0 | return result; |
284 | 0 | } |
285 | 0 | } |
286 | 0 | return nullptr; |
287 | 0 | } |
288 | | |
289 | | bool |
290 | | KeyframeEffect::HasAnimationOfProperty(nsCSSPropertyID aProperty) const |
291 | 0 | { |
292 | 0 | for (const AnimationProperty& property : mProperties) { |
293 | 0 | if (property.mProperty == aProperty) { |
294 | 0 | return true; |
295 | 0 | } |
296 | 0 | } |
297 | 0 | return false; |
298 | 0 | } |
299 | | |
300 | | #ifdef DEBUG |
301 | | bool |
302 | | SpecifiedKeyframeArraysAreEqual(const nsTArray<Keyframe>& aA, |
303 | | const nsTArray<Keyframe>& aB) |
304 | | { |
305 | | if (aA.Length() != aB.Length()) { |
306 | | return false; |
307 | | } |
308 | | |
309 | | for (size_t i = 0; i < aA.Length(); i++) { |
310 | | const Keyframe& a = aA[i]; |
311 | | const Keyframe& b = aB[i]; |
312 | | if (a.mOffset != b.mOffset || |
313 | | a.mTimingFunction != b.mTimingFunction || |
314 | | a.mPropertyValues != b.mPropertyValues) { |
315 | | return false; |
316 | | } |
317 | | } |
318 | | |
319 | | return true; |
320 | | } |
321 | | #endif |
322 | | |
323 | | void |
324 | | KeyframeEffect::UpdateProperties(const ComputedStyle* aStyle) |
325 | 0 | { |
326 | 0 | MOZ_ASSERT(aStyle); |
327 | 0 |
|
328 | 0 | nsTArray<AnimationProperty> properties = BuildProperties(aStyle); |
329 | 0 |
|
330 | 0 | // We need to update base styles even if any properties are not changed at all |
331 | 0 | // since base styles might have been changed due to parent style changes, etc. |
332 | 0 | EnsureBaseStyles(aStyle, properties); |
333 | 0 |
|
334 | 0 | if (mProperties == properties) { |
335 | 0 | return; |
336 | 0 | } |
337 | 0 | |
338 | 0 | // Preserve the state of the mIsRunningOnCompositor flag. |
339 | 0 | nsCSSPropertyIDSet runningOnCompositorProperties; |
340 | 0 |
|
341 | 0 | for (const AnimationProperty& property : mProperties) { |
342 | 0 | if (property.mIsRunningOnCompositor) { |
343 | 0 | runningOnCompositorProperties.AddProperty(property.mProperty); |
344 | 0 | } |
345 | 0 | } |
346 | 0 |
|
347 | 0 | mProperties = std::move(properties); |
348 | 0 | UpdateEffectSet(); |
349 | 0 |
|
350 | 0 | for (AnimationProperty& property : mProperties) { |
351 | 0 | property.mIsRunningOnCompositor = |
352 | 0 | runningOnCompositorProperties.HasProperty(property.mProperty); |
353 | 0 | } |
354 | 0 |
|
355 | 0 | CalculateCumulativeChangeHint(aStyle); |
356 | 0 |
|
357 | 0 | MarkCascadeNeedsUpdate(); |
358 | 0 |
|
359 | 0 | RequestRestyle(EffectCompositor::RestyleType::Layer); |
360 | 0 | } |
361 | | |
362 | | |
363 | | void |
364 | | KeyframeEffect::EnsureBaseStyles( |
365 | | const ComputedStyle* aComputedValues, |
366 | | const nsTArray<AnimationProperty>& aProperties) |
367 | 0 | { |
368 | 0 | if (!mTarget) { |
369 | 0 | return; |
370 | 0 | } |
371 | 0 | |
372 | 0 | mBaseStyleValuesForServo.Clear(); |
373 | 0 |
|
374 | 0 | nsPresContext* presContext = |
375 | 0 | nsContentUtils::GetContextForContent(mTarget->mElement); |
376 | 0 | // If |aProperties| is empty we're not going to dereference |presContext| so |
377 | 0 | // we don't care if it is nullptr. |
378 | 0 | // |
379 | 0 | // We could just return early when |aProperties| is empty and save looking up |
380 | 0 | // the pres context, but that won't save any effort normally since we don't |
381 | 0 | // call this function if we have no keyframes to begin with. Furthermore, the |
382 | 0 | // case where |presContext| is nullptr is so rare (we've only ever seen in |
383 | 0 | // fuzzing, and even then we've never been able to reproduce it reliably) |
384 | 0 | // it's not worth the runtime cost of an extra branch. |
385 | 0 | MOZ_ASSERT(presContext || aProperties.IsEmpty(), |
386 | 0 | "Typically presContext should not be nullptr but if it is" |
387 | 0 | " we should have also failed to calculate the computed values" |
388 | 0 | " passed-in as aProperties"); |
389 | 0 |
|
390 | 0 | RefPtr<ComputedStyle> baseComputedStyle; |
391 | 0 | for (const AnimationProperty& property : aProperties) { |
392 | 0 | EnsureBaseStyle(property, |
393 | 0 | presContext, |
394 | 0 | aComputedValues, |
395 | 0 | baseComputedStyle); |
396 | 0 | } |
397 | 0 | } |
398 | | |
399 | | void |
400 | | KeyframeEffect::EnsureBaseStyle( |
401 | | const AnimationProperty& aProperty, |
402 | | nsPresContext* aPresContext, |
403 | | const ComputedStyle* aComputedStyle, |
404 | | RefPtr<ComputedStyle>& aBaseComputedStyle) |
405 | 0 | { |
406 | 0 | bool hasAdditiveValues = false; |
407 | 0 |
|
408 | 0 | for (const AnimationPropertySegment& segment : aProperty.mSegments) { |
409 | 0 | if (!segment.HasReplaceableValues()) { |
410 | 0 | hasAdditiveValues = true; |
411 | 0 | break; |
412 | 0 | } |
413 | 0 | } |
414 | 0 |
|
415 | 0 | if (!hasAdditiveValues) { |
416 | 0 | return; |
417 | 0 | } |
418 | 0 | |
419 | 0 | if (!aBaseComputedStyle) { |
420 | 0 | Element* animatingElement = |
421 | 0 | EffectCompositor::GetElementToRestyle(mTarget->mElement, |
422 | 0 | mTarget->mPseudoType); |
423 | 0 | aBaseComputedStyle = aPresContext->StyleSet()-> |
424 | 0 | GetBaseContextForElement(animatingElement, aComputedStyle); |
425 | 0 | } |
426 | 0 | RefPtr<RawServoAnimationValue> baseValue = |
427 | 0 | Servo_ComputedValues_ExtractAnimationValue(aBaseComputedStyle, |
428 | 0 | aProperty.mProperty).Consume(); |
429 | 0 | mBaseStyleValuesForServo.Put(aProperty.mProperty, baseValue); |
430 | 0 | } |
431 | | |
432 | | void |
433 | | KeyframeEffect::WillComposeStyle() |
434 | 0 | { |
435 | 0 | ComputedTiming computedTiming = GetComputedTiming(); |
436 | 0 | mProgressOnLastCompose = computedTiming.mProgress; |
437 | 0 | mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration; |
438 | 0 | } |
439 | | |
440 | | |
441 | | void |
442 | | KeyframeEffect::ComposeStyleRule( |
443 | | RawServoAnimationValueMap& aAnimationValues, |
444 | | const AnimationProperty& aProperty, |
445 | | const AnimationPropertySegment& aSegment, |
446 | | const ComputedTiming& aComputedTiming) |
447 | 0 | { |
448 | 0 | Servo_AnimationCompose(&aAnimationValues, |
449 | 0 | &mBaseStyleValuesForServo, |
450 | 0 | aProperty.mProperty, |
451 | 0 | &aSegment, |
452 | 0 | &aProperty.mSegments.LastElement(), |
453 | 0 | &aComputedTiming, |
454 | 0 | mEffectOptions.mIterationComposite); |
455 | 0 | } |
456 | | |
457 | | void |
458 | | KeyframeEffect::ComposeStyle( |
459 | | RawServoAnimationValueMap& aComposeResult, |
460 | | const nsCSSPropertyIDSet& aPropertiesToSkip) |
461 | 0 | { |
462 | 0 | ComputedTiming computedTiming = GetComputedTiming(); |
463 | 0 |
|
464 | 0 | // If the progress is null, we don't have fill data for the current |
465 | 0 | // time so we shouldn't animate. |
466 | 0 | if (computedTiming.mProgress.IsNull()) { |
467 | 0 | return; |
468 | 0 | } |
469 | 0 | |
470 | 0 | for (size_t propIdx = 0, propEnd = mProperties.Length(); |
471 | 0 | propIdx != propEnd; ++propIdx) |
472 | 0 | { |
473 | 0 | const AnimationProperty& prop = mProperties[propIdx]; |
474 | 0 |
|
475 | 0 | MOZ_ASSERT(prop.mSegments[0].mFromKey == 0.0, "incorrect first from key"); |
476 | 0 | MOZ_ASSERT(prop.mSegments[prop.mSegments.Length() - 1].mToKey == 1.0, |
477 | 0 | "incorrect last to key"); |
478 | 0 |
|
479 | 0 | if (aPropertiesToSkip.HasProperty(prop.mProperty)) { |
480 | 0 | continue; |
481 | 0 | } |
482 | 0 | |
483 | 0 | MOZ_ASSERT(prop.mSegments.Length() > 0, |
484 | 0 | "property should not be in animations if it has no segments"); |
485 | 0 |
|
486 | 0 | // FIXME: Maybe cache the current segment? |
487 | 0 | const AnimationPropertySegment *segment = prop.mSegments.Elements(), |
488 | 0 | *segmentEnd = segment + prop.mSegments.Length(); |
489 | 0 | while (segment->mToKey <= computedTiming.mProgress.Value()) { |
490 | 0 | MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys"); |
491 | 0 | if ((segment+1) == segmentEnd) { |
492 | 0 | break; |
493 | 0 | } |
494 | 0 | ++segment; |
495 | 0 | MOZ_ASSERT(segment->mFromKey == (segment-1)->mToKey, "incorrect keys"); |
496 | 0 | } |
497 | 0 | MOZ_ASSERT(segment->mFromKey <= segment->mToKey, "incorrect keys"); |
498 | 0 | MOZ_ASSERT(segment >= prop.mSegments.Elements() && |
499 | 0 | size_t(segment - prop.mSegments.Elements()) < |
500 | 0 | prop.mSegments.Length(), |
501 | 0 | "out of array bounds"); |
502 | 0 |
|
503 | 0 | ComposeStyleRule(aComposeResult, prop, *segment, computedTiming); |
504 | 0 | } |
505 | 0 |
|
506 | 0 | // If the animation produces a change hint that affects the overflow region, |
507 | 0 | // we need to record the current time to unthrottle the animation |
508 | 0 | // periodically when the animation is being throttled because it's scrolled |
509 | 0 | // out of view. |
510 | 0 | if (HasPropertiesThatMightAffectOverflow()) { |
511 | 0 | nsPresContext* presContext = |
512 | 0 | nsContentUtils::GetContextForContent(mTarget->mElement); |
513 | 0 | if (presContext) { |
514 | 0 | TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh(); |
515 | 0 | EffectSet* effectSet = |
516 | 0 | EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); |
517 | 0 | MOZ_ASSERT(effectSet, "ComposeStyle should only be called on an effect " |
518 | 0 | "that is part of an effect set"); |
519 | 0 | effectSet->UpdateLastOverflowAnimationSyncTime(now); |
520 | 0 | } |
521 | 0 | } |
522 | 0 | } |
523 | | |
524 | | bool |
525 | | KeyframeEffect::IsRunningOnCompositor() const |
526 | 0 | { |
527 | 0 | // We consider animation is running on compositor if there is at least |
528 | 0 | // one property running on compositor. |
529 | 0 | // Animation.IsRunningOnCompotitor will return more fine grained |
530 | 0 | // information in bug 1196114. |
531 | 0 | for (const AnimationProperty& property : mProperties) { |
532 | 0 | if (property.mIsRunningOnCompositor) { |
533 | 0 | return true; |
534 | 0 | } |
535 | 0 | } |
536 | 0 | return false; |
537 | 0 | } |
538 | | |
539 | | void |
540 | | KeyframeEffect::SetIsRunningOnCompositor(nsCSSPropertyID aProperty, |
541 | | bool aIsRunning) |
542 | 0 | { |
543 | 0 | MOZ_ASSERT(nsCSSProps::PropHasFlags(aProperty, |
544 | 0 | CSSPropFlags::CanAnimateOnCompositor), |
545 | 0 | "Property being animated on compositor is a recognized " |
546 | 0 | "compositor-animatable property"); |
547 | 0 | for (AnimationProperty& property : mProperties) { |
548 | 0 | if (property.mProperty == aProperty) { |
549 | 0 | property.mIsRunningOnCompositor = aIsRunning; |
550 | 0 | // We currently only set a performance warning message when animations |
551 | 0 | // cannot be run on the compositor, so if this animation is running |
552 | 0 | // on the compositor we don't need a message. |
553 | 0 | if (aIsRunning) { |
554 | 0 | property.mPerformanceWarning.reset(); |
555 | 0 | } |
556 | 0 | return; |
557 | 0 | } |
558 | 0 | } |
559 | 0 | } |
560 | | |
561 | | void |
562 | | KeyframeEffect::ResetIsRunningOnCompositor() |
563 | 0 | { |
564 | 0 | for (AnimationProperty& property : mProperties) { |
565 | 0 | property.mIsRunningOnCompositor = false; |
566 | 0 | } |
567 | 0 | } |
568 | | |
569 | | static const KeyframeEffectOptions& |
570 | | KeyframeEffectOptionsFromUnion( |
571 | | const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions) |
572 | 0 | { |
573 | 0 | MOZ_ASSERT(aOptions.IsKeyframeEffectOptions()); |
574 | 0 | return aOptions.GetAsKeyframeEffectOptions(); |
575 | 0 | } |
576 | | |
577 | | static const KeyframeEffectOptions& |
578 | | KeyframeEffectOptionsFromUnion( |
579 | | const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions) |
580 | 0 | { |
581 | 0 | MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions()); |
582 | 0 | return aOptions.GetAsKeyframeAnimationOptions(); |
583 | 0 | } |
584 | | |
585 | | template <class OptionsType> |
586 | | static KeyframeEffectParams |
587 | | KeyframeEffectParamsFromUnion(const OptionsType& aOptions, |
588 | | CallerType aCallerType) |
589 | 0 | { |
590 | 0 | KeyframeEffectParams result; |
591 | 0 | if (aOptions.IsUnrestrictedDouble() || |
592 | 0 | // Ignore iterationComposite and composite if the corresponding pref is |
593 | 0 | // not set. The default value 'Replace' will be used instead. |
594 | 0 | !StaticPrefs::dom_animations_api_compositing_enabled()) { |
595 | 0 | return result; |
596 | 0 | } |
597 | 0 | |
598 | 0 | const KeyframeEffectOptions& options = |
599 | 0 | KeyframeEffectOptionsFromUnion(aOptions); |
600 | 0 | result.mIterationComposite = options.mIterationComposite; |
601 | 0 | result.mComposite = options.mComposite; |
602 | 0 | return result; |
603 | 0 | } Unexecuted instantiation: Unified_cpp_dom_animation0.cpp:mozilla::KeyframeEffectParams mozilla::dom::KeyframeEffectParamsFromUnion<mozilla::dom::UnrestrictedDoubleOrKeyframeEffectOptions>(mozilla::dom::UnrestrictedDoubleOrKeyframeEffectOptions const&, mozilla::dom::CallerType) Unexecuted instantiation: Unified_cpp_dom_animation0.cpp:mozilla::KeyframeEffectParams mozilla::dom::KeyframeEffectParamsFromUnion<mozilla::dom::UnrestrictedDoubleOrKeyframeAnimationOptions>(mozilla::dom::UnrestrictedDoubleOrKeyframeAnimationOptions const&, mozilla::dom::CallerType) |
604 | | |
605 | | /* static */ Maybe<OwningAnimationTarget> |
606 | | KeyframeEffect::ConvertTarget( |
607 | | const Nullable<ElementOrCSSPseudoElement>& aTarget) |
608 | 0 | { |
609 | 0 | // Return value optimization. |
610 | 0 | Maybe<OwningAnimationTarget> result; |
611 | 0 |
|
612 | 0 | if (aTarget.IsNull()) { |
613 | 0 | return result; |
614 | 0 | } |
615 | 0 | |
616 | 0 | const ElementOrCSSPseudoElement& target = aTarget.Value(); |
617 | 0 | MOZ_ASSERT(target.IsElement() || target.IsCSSPseudoElement(), |
618 | 0 | "Uninitialized target"); |
619 | 0 |
|
620 | 0 | if (target.IsElement()) { |
621 | 0 | result.emplace(&target.GetAsElement()); |
622 | 0 | } else { |
623 | 0 | RefPtr<Element> elem = target.GetAsCSSPseudoElement().ParentElement(); |
624 | 0 | result.emplace(elem, target.GetAsCSSPseudoElement().GetType()); |
625 | 0 | } |
626 | 0 | return result; |
627 | 0 | } |
628 | | |
629 | | template <class OptionsType> |
630 | | /* static */ already_AddRefed<KeyframeEffect> |
631 | | KeyframeEffect::ConstructKeyframeEffect( |
632 | | const GlobalObject& aGlobal, |
633 | | const Nullable<ElementOrCSSPseudoElement>& aTarget, |
634 | | JS::Handle<JSObject*> aKeyframes, |
635 | | const OptionsType& aOptions, |
636 | | ErrorResult& aRv) |
637 | 0 | { |
638 | 0 | // We should get the document from `aGlobal` instead of the current Realm |
639 | 0 | // to make this works in Xray case. |
640 | 0 | // |
641 | 0 | // In all non-Xray cases, `aGlobal` matches the current Realm, so this |
642 | 0 | // matches the spec behavior. |
643 | 0 | // |
644 | 0 | // In Xray case, the new objects should be created using the document of |
645 | 0 | // the target global, but the KeyframeEffect constructors are called in the |
646 | 0 | // caller's compartment to access `aKeyframes` object. |
647 | 0 | nsIDocument* doc = AnimationUtils::GetDocumentFromGlobal(aGlobal.Get()); |
648 | 0 | if (!doc) { |
649 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
650 | 0 | return nullptr; |
651 | 0 | } |
652 | 0 | |
653 | 0 | TimingParams timingParams = |
654 | 0 | TimingParams::FromOptionsUnion(aOptions, doc, aRv); |
655 | 0 | if (aRv.Failed()) { |
656 | 0 | return nullptr; |
657 | 0 | } |
658 | 0 | |
659 | 0 | KeyframeEffectParams effectOptions = |
660 | 0 | KeyframeEffectParamsFromUnion(aOptions, aGlobal.CallerType()); |
661 | 0 |
|
662 | 0 | Maybe<OwningAnimationTarget> target = ConvertTarget(aTarget); |
663 | 0 | RefPtr<KeyframeEffect> effect = |
664 | 0 | new KeyframeEffect(doc, target, timingParams, effectOptions); |
665 | 0 |
|
666 | 0 | effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv); |
667 | 0 | if (aRv.Failed()) { |
668 | 0 | return nullptr; |
669 | 0 | } |
670 | 0 | |
671 | 0 | return effect.forget(); |
672 | 0 | } Unexecuted instantiation: already_AddRefed<mozilla::dom::KeyframeEffect> mozilla::dom::KeyframeEffect::ConstructKeyframeEffect<mozilla::dom::UnrestrictedDoubleOrKeyframeEffectOptions>(mozilla::dom::GlobalObject const&, mozilla::dom::Nullable<mozilla::dom::ElementOrCSSPseudoElement> const&, JS::Handle<JSObject*>, mozilla::dom::UnrestrictedDoubleOrKeyframeEffectOptions const&, mozilla::ErrorResult&) Unexecuted instantiation: already_AddRefed<mozilla::dom::KeyframeEffect> mozilla::dom::KeyframeEffect::ConstructKeyframeEffect<mozilla::dom::UnrestrictedDoubleOrKeyframeAnimationOptions>(mozilla::dom::GlobalObject const&, mozilla::dom::Nullable<mozilla::dom::ElementOrCSSPseudoElement> const&, JS::Handle<JSObject*>, mozilla::dom::UnrestrictedDoubleOrKeyframeAnimationOptions const&, mozilla::ErrorResult&) |
673 | | |
674 | | nsTArray<AnimationProperty> |
675 | | KeyframeEffect::BuildProperties(const ComputedStyle* aStyle) |
676 | 0 | { |
677 | 0 |
|
678 | 0 | MOZ_ASSERT(aStyle); |
679 | 0 |
|
680 | 0 | nsTArray<AnimationProperty> result; |
681 | 0 | // If mTarget is null, return an empty property array. |
682 | 0 | if (!mTarget) { |
683 | 0 | return result; |
684 | 0 | } |
685 | 0 | |
686 | 0 | // When GetComputedKeyframeValues or GetAnimationPropertiesFromKeyframes |
687 | 0 | // calculate computed values from |mKeyframes|, they could possibly |
688 | 0 | // trigger a subsequent restyle in which we rebuild animations. If that |
689 | 0 | // happens we could find that |mKeyframes| is overwritten while it is |
690 | 0 | // being iterated over. Normally that shouldn't happen but just in case we |
691 | 0 | // make a copy of |mKeyframes| first and iterate over that instead. |
692 | 0 | auto keyframesCopy(mKeyframes); |
693 | 0 |
|
694 | 0 | result = |
695 | 0 | KeyframeUtils::GetAnimationPropertiesFromKeyframes( |
696 | 0 | keyframesCopy, |
697 | 0 | mTarget->mElement, |
698 | 0 | aStyle, |
699 | 0 | mEffectOptions.mComposite); |
700 | 0 |
|
701 | | #ifdef DEBUG |
702 | | MOZ_ASSERT(SpecifiedKeyframeArraysAreEqual(mKeyframes, keyframesCopy), |
703 | | "Apart from the computed offset members, the keyframes array" |
704 | | " should not be modified"); |
705 | | #endif |
706 | |
|
707 | 0 | mKeyframes.SwapElements(keyframesCopy); |
708 | 0 | return result; |
709 | 0 | } |
710 | | |
711 | | template<typename FrameEnumFunc> |
712 | | static void |
713 | | EnumerateContinuationsOrIBSplitSiblings(nsIFrame* aFrame, |
714 | | FrameEnumFunc&& aFunc) |
715 | 0 | { |
716 | 0 | while (aFrame) { |
717 | 0 | aFunc(aFrame); |
718 | 0 | aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame); |
719 | 0 | } |
720 | 0 | } Unexecuted instantiation: Unified_cpp_dom_animation0.cpp:void mozilla::dom::EnumerateContinuationsOrIBSplitSiblings<mozilla::dom::KeyframeEffect::UpdateTargetRegistration()::$_6>(nsIFrame*, mozilla::dom::KeyframeEffect::UpdateTargetRegistration()::$_6&&) Unexecuted instantiation: Unified_cpp_dom_animation0.cpp:void mozilla::dom::EnumerateContinuationsOrIBSplitSiblings<mozilla::dom::KeyframeEffect::UnregisterTarget()::$_7>(nsIFrame*, mozilla::dom::KeyframeEffect::UnregisterTarget()::$_7&&) Unexecuted instantiation: Unified_cpp_dom_animation0.cpp:void mozilla::dom::EnumerateContinuationsOrIBSplitSiblings<mozilla::dom::KeyframeEffect::UpdateEffectSet(mozilla::EffectSet*) const::$_8>(nsIFrame*, mozilla::dom::KeyframeEffect::UpdateEffectSet(mozilla::EffectSet*) const::$_8&&) Unexecuted instantiation: Unified_cpp_dom_animation0.cpp:void mozilla::dom::EnumerateContinuationsOrIBSplitSiblings<mozilla::dom::KeyframeEffect::UpdateEffectSet(mozilla::EffectSet*) const::$_9>(nsIFrame*, mozilla::dom::KeyframeEffect::UpdateEffectSet(mozilla::EffectSet*) const::$_9&&) |
721 | | |
722 | | void |
723 | | KeyframeEffect::UpdateTargetRegistration() |
724 | 0 | { |
725 | 0 | if (!mTarget) { |
726 | 0 | return; |
727 | 0 | } |
728 | 0 | |
729 | 0 | bool isRelevant = mAnimation && mAnimation->IsRelevant(); |
730 | 0 |
|
731 | 0 | // Animation::IsRelevant() returns a cached value. It only updates when |
732 | 0 | // something calls Animation::UpdateRelevance. Whenever our timing changes, |
733 | 0 | // we should be notifying our Animation before calling this, so |
734 | 0 | // Animation::IsRelevant() should be up-to-date by the time we get here. |
735 | 0 | MOZ_ASSERT(isRelevant == IsCurrent() || IsInEffect(), |
736 | 0 | "Out of date Animation::IsRelevant value"); |
737 | 0 |
|
738 | 0 | if (isRelevant && !mInEffectSet) { |
739 | 0 | EffectSet* effectSet = |
740 | 0 | EffectSet::GetOrCreateEffectSet(mTarget->mElement, mTarget->mPseudoType); |
741 | 0 | effectSet->AddEffect(*this); |
742 | 0 | mInEffectSet = true; |
743 | 0 | UpdateEffectSet(effectSet); |
744 | 0 | nsIFrame* frame = GetPrimaryFrame(); |
745 | 0 | EnumerateContinuationsOrIBSplitSiblings(frame, |
746 | 0 | [](nsIFrame* aFrame) { |
747 | 0 | aFrame->MarkNeedsDisplayItemRebuild(); |
748 | 0 | } |
749 | 0 | ); |
750 | 0 | } else if (!isRelevant && mInEffectSet) { |
751 | 0 | UnregisterTarget(); |
752 | 0 | } |
753 | 0 | } |
754 | | |
755 | | void |
756 | | KeyframeEffect::UnregisterTarget() |
757 | 0 | { |
758 | 0 | if (!mInEffectSet) { |
759 | 0 | return; |
760 | 0 | } |
761 | 0 | |
762 | 0 | EffectSet* effectSet = |
763 | 0 | EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); |
764 | 0 | MOZ_ASSERT(effectSet, "If mInEffectSet is true, there must be an EffectSet" |
765 | 0 | " on the target element"); |
766 | 0 | mInEffectSet = false; |
767 | 0 | if (effectSet) { |
768 | 0 | effectSet->RemoveEffect(*this); |
769 | 0 |
|
770 | 0 | if (effectSet->IsEmpty()) { |
771 | 0 | EffectSet::DestroyEffectSet(mTarget->mElement, mTarget->mPseudoType); |
772 | 0 | } |
773 | 0 | } |
774 | 0 | nsIFrame* frame = GetPrimaryFrame(); |
775 | 0 | EnumerateContinuationsOrIBSplitSiblings(frame, |
776 | 0 | [](nsIFrame* aFrame) { |
777 | 0 | aFrame->MarkNeedsDisplayItemRebuild(); |
778 | 0 | } |
779 | 0 | ); |
780 | 0 | } |
781 | | |
782 | | void |
783 | | KeyframeEffect::RequestRestyle(EffectCompositor::RestyleType aRestyleType) |
784 | 0 | { |
785 | 0 | if (!mTarget) { |
786 | 0 | return; |
787 | 0 | } |
788 | 0 | nsPresContext* presContext = nsContentUtils::GetContextForContent(mTarget->mElement); |
789 | 0 | if (presContext && mAnimation) { |
790 | 0 | presContext->EffectCompositor()-> |
791 | 0 | RequestRestyle(mTarget->mElement, mTarget->mPseudoType, |
792 | 0 | aRestyleType, mAnimation->CascadeLevel()); |
793 | 0 | } |
794 | 0 | } |
795 | | |
796 | | already_AddRefed<ComputedStyle> |
797 | | KeyframeEffect::GetTargetComputedStyle() |
798 | 0 | { |
799 | 0 | if (!GetRenderedDocument()) { |
800 | 0 | return nullptr; |
801 | 0 | } |
802 | 0 | |
803 | 0 | MOZ_ASSERT(mTarget, |
804 | 0 | "Should only have a document when we have a target element"); |
805 | 0 |
|
806 | 0 | nsAtom* pseudo = mTarget->mPseudoType < CSSPseudoElementType::Count |
807 | 0 | ? nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType) |
808 | 0 | : nullptr; |
809 | 0 |
|
810 | 0 | OwningAnimationTarget kungfuDeathGrip(mTarget->mElement, |
811 | 0 | mTarget->mPseudoType); |
812 | 0 |
|
813 | 0 | return nsComputedDOMStyle::GetComputedStyle(mTarget->mElement, pseudo); |
814 | 0 | } |
815 | | |
816 | | #ifdef DEBUG |
817 | | void |
818 | | DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties) |
819 | | { |
820 | | for (auto& p : aAnimationProperties) { |
821 | | printf("%s\n", nsCString(nsCSSProps::GetStringValue(p.mProperty)).get()); |
822 | | for (auto& s : p.mSegments) { |
823 | | nsString fromValue, toValue; |
824 | | s.mFromValue.SerializeSpecifiedValue(p.mProperty, fromValue); |
825 | | s.mToValue.SerializeSpecifiedValue(p.mProperty, toValue); |
826 | | printf(" %f..%f: %s..%s\n", s.mFromKey, s.mToKey, |
827 | | NS_ConvertUTF16toUTF8(fromValue).get(), |
828 | | NS_ConvertUTF16toUTF8(toValue).get()); |
829 | | } |
830 | | } |
831 | | } |
832 | | #endif |
833 | | |
834 | | /* static */ already_AddRefed<KeyframeEffect> |
835 | | KeyframeEffect::Constructor( |
836 | | const GlobalObject& aGlobal, |
837 | | const Nullable<ElementOrCSSPseudoElement>& aTarget, |
838 | | JS::Handle<JSObject*> aKeyframes, |
839 | | const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions, |
840 | | ErrorResult& aRv) |
841 | 0 | { |
842 | 0 | return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv); |
843 | 0 | } |
844 | | |
845 | | /* static */ already_AddRefed<KeyframeEffect> |
846 | | KeyframeEffect::Constructor( |
847 | | const GlobalObject& aGlobal, |
848 | | const Nullable<ElementOrCSSPseudoElement>& aTarget, |
849 | | JS::Handle<JSObject*> aKeyframes, |
850 | | const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions, |
851 | | ErrorResult& aRv) |
852 | 0 | { |
853 | 0 | return ConstructKeyframeEffect(aGlobal, aTarget, aKeyframes, aOptions, aRv); |
854 | 0 | } |
855 | | |
856 | | /* static */ already_AddRefed<KeyframeEffect> |
857 | | KeyframeEffect::Constructor(const GlobalObject& aGlobal, |
858 | | KeyframeEffect& aSource, |
859 | | ErrorResult& aRv) |
860 | 0 | { |
861 | 0 | nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context()); |
862 | 0 | if (!doc) { |
863 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
864 | 0 | return nullptr; |
865 | 0 | } |
866 | 0 | |
867 | 0 | // Create a new KeyframeEffect object with aSource's target, |
868 | 0 | // iteration composite operation, composite operation, and spacing mode. |
869 | 0 | // The constructor creates a new AnimationEffect object by |
870 | 0 | // aSource's TimingParams. |
871 | 0 | // Note: we don't need to re-throw exceptions since the value specified on |
872 | 0 | // aSource's timing object can be assumed valid. |
873 | 0 | RefPtr<KeyframeEffect> effect = |
874 | 0 | new KeyframeEffect(doc, |
875 | 0 | aSource.mTarget, |
876 | 0 | aSource.SpecifiedTiming(), |
877 | 0 | aSource.mEffectOptions); |
878 | 0 | // Copy cumulative change hint. mCumulativeChangeHint should be the same as |
879 | 0 | // the source one because both of targets are the same. |
880 | 0 | effect->mCumulativeChangeHint = aSource.mCumulativeChangeHint; |
881 | 0 |
|
882 | 0 | // Copy aSource's keyframes and animation properties. |
883 | 0 | // Note: We don't call SetKeyframes directly, which might revise the |
884 | 0 | // computed offsets and rebuild the animation properties. |
885 | 0 | effect->mKeyframes = aSource.mKeyframes; |
886 | 0 | effect->mProperties = aSource.mProperties; |
887 | 0 | return effect.forget(); |
888 | 0 | } |
889 | | |
890 | | void |
891 | | KeyframeEffect::GetTarget(Nullable<OwningElementOrCSSPseudoElement>& aRv) const |
892 | 0 | { |
893 | 0 | if (!mTarget) { |
894 | 0 | aRv.SetNull(); |
895 | 0 | return; |
896 | 0 | } |
897 | 0 | |
898 | 0 | switch (mTarget->mPseudoType) { |
899 | 0 | case CSSPseudoElementType::before: |
900 | 0 | case CSSPseudoElementType::after: |
901 | 0 | aRv.SetValue().SetAsCSSPseudoElement() = |
902 | 0 | CSSPseudoElement::GetCSSPseudoElement(mTarget->mElement, |
903 | 0 | mTarget->mPseudoType); |
904 | 0 | break; |
905 | 0 |
|
906 | 0 | case CSSPseudoElementType::NotPseudo: |
907 | 0 | aRv.SetValue().SetAsElement() = mTarget->mElement; |
908 | 0 | break; |
909 | 0 |
|
910 | 0 | default: |
911 | 0 | MOZ_ASSERT_UNREACHABLE("Animation of unsupported pseudo-type"); |
912 | 0 | aRv.SetNull(); |
913 | 0 | } |
914 | 0 | } |
915 | | |
916 | | void |
917 | | KeyframeEffect::SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget) |
918 | 0 | { |
919 | 0 | Maybe<OwningAnimationTarget> newTarget = ConvertTarget(aTarget); |
920 | 0 | if (mTarget == newTarget) { |
921 | 0 | // Assign the same target, skip it. |
922 | 0 | return; |
923 | 0 | } |
924 | 0 | |
925 | 0 | if (mTarget) { |
926 | 0 | UnregisterTarget(); |
927 | 0 | ResetIsRunningOnCompositor(); |
928 | 0 |
|
929 | 0 | RequestRestyle(EffectCompositor::RestyleType::Layer); |
930 | 0 |
|
931 | 0 | nsAutoAnimationMutationBatch mb(mTarget->mElement->OwnerDoc()); |
932 | 0 | if (mAnimation) { |
933 | 0 | nsNodeUtils::AnimationRemoved(mAnimation); |
934 | 0 | } |
935 | 0 | } |
936 | 0 |
|
937 | 0 | mTarget = newTarget; |
938 | 0 |
|
939 | 0 | if (mTarget) { |
940 | 0 | UpdateTargetRegistration(); |
941 | 0 | RefPtr<ComputedStyle> computedStyle = GetTargetComputedStyle(); |
942 | 0 | if (computedStyle) { |
943 | 0 | UpdateProperties(computedStyle); |
944 | 0 | } |
945 | 0 |
|
946 | 0 | MaybeUpdateFrameForCompositor(); |
947 | 0 |
|
948 | 0 | RequestRestyle(EffectCompositor::RestyleType::Layer); |
949 | 0 |
|
950 | 0 | nsAutoAnimationMutationBatch mb(mTarget->mElement->OwnerDoc()); |
951 | 0 | if (mAnimation) { |
952 | 0 | nsNodeUtils::AnimationAdded(mAnimation); |
953 | 0 | mAnimation->ReschedulePendingTasks(); |
954 | 0 | } |
955 | 0 | } |
956 | 0 | } |
957 | | |
958 | | static void |
959 | | CreatePropertyValue(nsCSSPropertyID aProperty, |
960 | | float aOffset, |
961 | | const Maybe<ComputedTimingFunction>& aTimingFunction, |
962 | | const AnimationValue& aValue, |
963 | | dom::CompositeOperation aComposite, |
964 | | AnimationPropertyValueDetails& aResult) |
965 | 0 | { |
966 | 0 | aResult.mOffset = aOffset; |
967 | 0 |
|
968 | 0 | if (!aValue.IsNull()) { |
969 | 0 | nsString stringValue; |
970 | 0 | aValue.SerializeSpecifiedValue(aProperty, stringValue); |
971 | 0 | aResult.mValue.Construct(stringValue); |
972 | 0 | } |
973 | 0 |
|
974 | 0 | if (aTimingFunction) { |
975 | 0 | aResult.mEasing.Construct(); |
976 | 0 | aTimingFunction->AppendToString(aResult.mEasing.Value()); |
977 | 0 | } else { |
978 | 0 | aResult.mEasing.Construct(NS_LITERAL_STRING("linear")); |
979 | 0 | } |
980 | 0 |
|
981 | 0 | aResult.mComposite = aComposite; |
982 | 0 | } |
983 | | |
984 | | void |
985 | | KeyframeEffect::GetProperties( |
986 | | nsTArray<AnimationPropertyDetails>& aProperties, |
987 | | ErrorResult& aRv) const |
988 | 0 | { |
989 | 0 | for (const AnimationProperty& property : mProperties) { |
990 | 0 | AnimationPropertyDetails propertyDetails; |
991 | 0 | propertyDetails.mProperty = |
992 | 0 | NS_ConvertASCIItoUTF16(nsCSSProps::GetStringValue(property.mProperty)); |
993 | 0 | propertyDetails.mRunningOnCompositor = property.mIsRunningOnCompositor; |
994 | 0 |
|
995 | 0 | nsAutoString localizedString; |
996 | 0 | if (property.mPerformanceWarning && |
997 | 0 | property.mPerformanceWarning->ToLocalizedString(localizedString)) { |
998 | 0 | propertyDetails.mWarning.Construct(localizedString); |
999 | 0 | } |
1000 | 0 |
|
1001 | 0 | if (!propertyDetails.mValues.SetCapacity(property.mSegments.Length(), |
1002 | 0 | mozilla::fallible)) { |
1003 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
1004 | 0 | return; |
1005 | 0 | } |
1006 | 0 | |
1007 | 0 | for (size_t segmentIdx = 0, segmentLen = property.mSegments.Length(); |
1008 | 0 | segmentIdx < segmentLen; |
1009 | 0 | segmentIdx++) |
1010 | 0 | { |
1011 | 0 | const AnimationPropertySegment& segment = property.mSegments[segmentIdx]; |
1012 | 0 |
|
1013 | 0 | binding_detail::FastAnimationPropertyValueDetails fromValue; |
1014 | 0 | CreatePropertyValue(property.mProperty, segment.mFromKey, |
1015 | 0 | segment.mTimingFunction, segment.mFromValue, |
1016 | 0 | segment.mFromComposite, fromValue); |
1017 | 0 | // We don't apply timing functions for zero-length segments, so |
1018 | 0 | // don't return one here. |
1019 | 0 | if (segment.mFromKey == segment.mToKey) { |
1020 | 0 | fromValue.mEasing.Reset(); |
1021 | 0 | } |
1022 | 0 | // The following won't fail since we have already allocated the capacity |
1023 | 0 | // above. |
1024 | 0 | propertyDetails.mValues.AppendElement(fromValue, mozilla::fallible); |
1025 | 0 |
|
1026 | 0 | // Normally we can ignore the to-value for this segment since it is |
1027 | 0 | // identical to the from-value from the next segment. However, we need |
1028 | 0 | // to add it if either: |
1029 | 0 | // a) this is the last segment, or |
1030 | 0 | // b) the next segment's from-value differs. |
1031 | 0 | if (segmentIdx == segmentLen - 1 || |
1032 | 0 | property.mSegments[segmentIdx + 1].mFromValue != segment.mToValue) { |
1033 | 0 | binding_detail::FastAnimationPropertyValueDetails toValue; |
1034 | 0 | CreatePropertyValue(property.mProperty, segment.mToKey, |
1035 | 0 | Nothing(), segment.mToValue, |
1036 | 0 | segment.mToComposite, toValue); |
1037 | 0 | // It doesn't really make sense to have a timing function on the |
1038 | 0 | // last property value or before a sudden jump so we just drop the |
1039 | 0 | // easing property altogether. |
1040 | 0 | toValue.mEasing.Reset(); |
1041 | 0 | propertyDetails.mValues.AppendElement(toValue, mozilla::fallible); |
1042 | 0 | } |
1043 | 0 | } |
1044 | 0 |
|
1045 | 0 | aProperties.AppendElement(propertyDetails); |
1046 | 0 | } |
1047 | 0 | } |
1048 | | |
1049 | | void |
1050 | | KeyframeEffect::GetKeyframes(JSContext*& aCx, |
1051 | | nsTArray<JSObject*>& aResult, |
1052 | | ErrorResult& aRv) |
1053 | 0 | { |
1054 | 0 | MOZ_ASSERT(aResult.IsEmpty()); |
1055 | 0 | MOZ_ASSERT(!aRv.Failed()); |
1056 | 0 |
|
1057 | 0 | if (!aResult.SetCapacity(mKeyframes.Length(), mozilla::fallible)) { |
1058 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
1059 | 0 | return; |
1060 | 0 | } |
1061 | 0 | |
1062 | 0 | bool isCSSAnimation = mAnimation && mAnimation->AsCSSAnimation(); |
1063 | 0 |
|
1064 | 0 | // For Servo, when we have CSS Animation @keyframes with variables, we convert |
1065 | 0 | // shorthands to longhands if needed, and store a reference to the unparsed |
1066 | 0 | // value. When it comes time to serialize, however, what do you serialize for |
1067 | 0 | // a longhand that comes from a variable reference in a shorthand? Servo says, |
1068 | 0 | // "an empty string" which is not particularly helpful. |
1069 | 0 | // |
1070 | 0 | // We should just store shorthands as-is (bug 1391537) and then return the |
1071 | 0 | // variable references, but for now, since we don't do that, and in order to |
1072 | 0 | // be consistent with Gecko, we just expand the variables (assuming we have |
1073 | 0 | // enough context to do so). For that we need to grab the ComputedStyle so we |
1074 | 0 | // know what custom property values to provide. |
1075 | 0 | RefPtr<ComputedStyle> computedStyle; |
1076 | 0 | if (isCSSAnimation) { |
1077 | 0 | // The following will flush style but that's ok since if you update |
1078 | 0 | // a variable's computed value, you expect to see that updated value in the |
1079 | 0 | // result of getKeyframes(). |
1080 | 0 | // |
1081 | 0 | // If we don't have a target, the following will return null. In that case |
1082 | 0 | // we might end up returning variables as-is or empty string. That should be |
1083 | 0 | // acceptable however, since such a case is rare and this is only |
1084 | 0 | // short-term (and unshipped) behavior until bug 1391537 is fixed. |
1085 | 0 | computedStyle = GetTargetComputedStyle(); |
1086 | 0 | } |
1087 | 0 |
|
1088 | 0 | for (const Keyframe& keyframe : mKeyframes) { |
1089 | 0 | // Set up a dictionary object for the explicit members |
1090 | 0 | BaseComputedKeyframe keyframeDict; |
1091 | 0 | if (keyframe.mOffset) { |
1092 | 0 | keyframeDict.mOffset.SetValue(keyframe.mOffset.value()); |
1093 | 0 | } |
1094 | 0 | MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet, |
1095 | 0 | "Invalid computed offset"); |
1096 | 0 | keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset); |
1097 | 0 | if (keyframe.mTimingFunction) { |
1098 | 0 | keyframeDict.mEasing.Truncate(); |
1099 | 0 | keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing); |
1100 | 0 | } // else if null, leave easing as its default "linear". |
1101 | 0 |
|
1102 | 0 | keyframeDict.mComposite = keyframe.mComposite; |
1103 | 0 |
|
1104 | 0 | JS::Rooted<JS::Value> keyframeJSValue(aCx); |
1105 | 0 | if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) { |
1106 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
1107 | 0 | return; |
1108 | 0 | } |
1109 | 0 | |
1110 | 0 | RefPtr<RawServoDeclarationBlock> customProperties; |
1111 | 0 | // A workaround for CSS Animations in servo backend, custom properties in |
1112 | 0 | // keyframe are stored in a servo's declaration block. Find the declaration |
1113 | 0 | // block to resolve CSS variables in the keyframe. |
1114 | 0 | // This workaround will be solved by bug 1391537. |
1115 | 0 | if (isCSSAnimation) { |
1116 | 0 | for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) { |
1117 | 0 | if (propertyValue.mProperty == |
1118 | 0 | nsCSSPropertyID::eCSSPropertyExtra_variable) { |
1119 | 0 | customProperties = propertyValue.mServoDeclarationBlock; |
1120 | 0 | break; |
1121 | 0 | } |
1122 | 0 | } |
1123 | 0 | } |
1124 | 0 |
|
1125 | 0 | JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject()); |
1126 | 0 | for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) { |
1127 | 0 | nsAutoString stringValue; |
1128 | 0 | // Don't serialize the custom properties for this keyframe. |
1129 | 0 | if (propertyValue.mProperty == |
1130 | 0 | nsCSSPropertyID::eCSSPropertyExtra_variable) { |
1131 | 0 | continue; |
1132 | 0 | } |
1133 | 0 | if (propertyValue.mServoDeclarationBlock) { |
1134 | 0 | Servo_DeclarationBlock_SerializeOneValue( |
1135 | 0 | propertyValue.mServoDeclarationBlock, |
1136 | 0 | propertyValue.mProperty, |
1137 | 0 | &stringValue, |
1138 | 0 | computedStyle, |
1139 | 0 | customProperties); |
1140 | 0 | } else { |
1141 | 0 | RawServoAnimationValue* value = |
1142 | 0 | mBaseStyleValuesForServo.GetWeak(propertyValue.mProperty); |
1143 | 0 |
|
1144 | 0 | if (value) { |
1145 | 0 | Servo_AnimationValue_Serialize(value, |
1146 | 0 | propertyValue.mProperty, |
1147 | 0 | &stringValue); |
1148 | 0 | } |
1149 | 0 | } |
1150 | 0 |
|
1151 | 0 | const char* name = nsCSSProps::PropertyIDLName(propertyValue.mProperty); |
1152 | 0 | JS::Rooted<JS::Value> value(aCx); |
1153 | 0 | if (!ToJSValue(aCx, stringValue, &value) || |
1154 | 0 | !JS_DefineProperty(aCx, keyframeObject, name, value, |
1155 | 0 | JSPROP_ENUMERATE)) { |
1156 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
1157 | 0 | return; |
1158 | 0 | } |
1159 | 0 | } |
1160 | 0 |
|
1161 | 0 | aResult.AppendElement(keyframeObject); |
1162 | 0 | } |
1163 | 0 | } |
1164 | | |
1165 | | /* static */ const TimeDuration |
1166 | | KeyframeEffect::OverflowRegionRefreshInterval() |
1167 | 0 | { |
1168 | 0 | // The amount of time we can wait between updating throttled animations |
1169 | 0 | // on the main thread that influence the overflow region. |
1170 | 0 | static const TimeDuration kOverflowRegionRefreshInterval = |
1171 | 0 | TimeDuration::FromMilliseconds(200); |
1172 | 0 |
|
1173 | 0 | return kOverflowRegionRefreshInterval; |
1174 | 0 | } |
1175 | | |
1176 | | bool |
1177 | | KeyframeEffect::CanThrottleIfNotVisible(nsIFrame& aFrame) const |
1178 | 0 | { |
1179 | 0 | // Unless we are newly in-effect, we can throttle the animation if the |
1180 | 0 | // animation is paint only and the target frame is out of view or the document |
1181 | 0 | // is in background tabs. |
1182 | 0 | if (!mInEffectOnLastAnimationTimingUpdate || !CanIgnoreIfNotVisible()) { |
1183 | 0 | return false; |
1184 | 0 | } |
1185 | 0 | |
1186 | 0 | nsIPresShell* presShell = GetPresShell(); |
1187 | 0 | if (presShell && !presShell->IsActive()) { |
1188 | 0 | return true; |
1189 | 0 | } |
1190 | 0 | |
1191 | 0 | const bool isVisibilityHidden = |
1192 | 0 | !aFrame.IsVisibleOrMayHaveVisibleDescendants(); |
1193 | 0 | if ((!isVisibilityHidden || HasVisibilityChange()) && |
1194 | 0 | !aFrame.IsScrolledOutOfView()) { |
1195 | 0 | return false; |
1196 | 0 | } |
1197 | 0 | |
1198 | 0 | // If there are no overflow change hints, we don't need to worry about |
1199 | 0 | // unthrottling the animation periodically to update scrollbar positions for |
1200 | 0 | // the overflow region. |
1201 | 0 | if (!HasPropertiesThatMightAffectOverflow()) { |
1202 | 0 | return true; |
1203 | 0 | } |
1204 | 0 | |
1205 | 0 | // Don't throttle finite animations since the animation might suddenly |
1206 | 0 | // come into view and if it was throttled it will be out-of-sync. |
1207 | 0 | if (HasFiniteActiveDuration()) { |
1208 | 0 | return false; |
1209 | 0 | } |
1210 | 0 | |
1211 | 0 | return isVisibilityHidden |
1212 | 0 | ? CanThrottleOverflowChangesInScrollable(aFrame) |
1213 | 0 | : CanThrottleOverflowChanges(aFrame); |
1214 | 0 | } |
1215 | | |
1216 | | bool |
1217 | | KeyframeEffect::CanThrottle() const |
1218 | 0 | { |
1219 | 0 | // Unthrottle if we are not in effect or current. This will be the case when |
1220 | 0 | // our owning animation has finished, is idle, or when we are in the delay |
1221 | 0 | // phase (but without a backwards fill). In each case the computed progress |
1222 | 0 | // value produced on each tick will be the same so we will skip requesting |
1223 | 0 | // unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get |
1224 | 0 | // here will be because of a change in state (e.g. we are newly finished or |
1225 | 0 | // newly no longer in effect) in which case we shouldn't throttle the sample. |
1226 | 0 | if (!IsInEffect() || !IsCurrent()) { |
1227 | 0 | return false; |
1228 | 0 | } |
1229 | 0 | |
1230 | 0 | nsIFrame* frame = GetStyleFrame(); |
1231 | 0 | if (!frame) { |
1232 | 0 | // There are two possible cases here. |
1233 | 0 | // a) No target element |
1234 | 0 | // b) The target element has no frame, e.g. because it is in a display:none |
1235 | 0 | // subtree. |
1236 | 0 | // In either case we can throttle the animation because there is no |
1237 | 0 | // need to update on the main thread. |
1238 | 0 | return true; |
1239 | 0 | } |
1240 | 0 | |
1241 | 0 | if (CanThrottleIfNotVisible(*frame)) { |
1242 | 0 | return true; |
1243 | 0 | } |
1244 | 0 | |
1245 | 0 | // First we need to check layer generation and transform overflow |
1246 | 0 | // prior to the property.mIsRunningOnCompositor check because we should |
1247 | 0 | // occasionally unthrottle these animations even if the animations are |
1248 | 0 | // already running on compositor. |
1249 | 0 | for (const LayerAnimationInfo::Record& record : |
1250 | 0 | LayerAnimationInfo::sRecords) { |
1251 | 0 | // Skip properties that are overridden by !important rules. |
1252 | 0 | // (GetEffectiveAnimationOfProperty, as called by |
1253 | 0 | // HasEffectiveAnimationOfProperty, only returns a property which is |
1254 | 0 | // neither overridden by !important rules nor overridden by other |
1255 | 0 | // animation.) |
1256 | 0 | if (!HasEffectiveAnimationOfProperty(record.mProperty)) { |
1257 | 0 | continue; |
1258 | 0 | } |
1259 | 0 | |
1260 | 0 | EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, |
1261 | 0 | mTarget->mPseudoType); |
1262 | 0 | MOZ_ASSERT(effectSet, "CanThrottle should be called on an effect " |
1263 | 0 | "associated with a target element"); |
1264 | 0 | // Note that AnimationInfo::GetGenarationFromFrame() is supposed to work |
1265 | 0 | // with the primary frame instead of the style frame. |
1266 | 0 | Maybe<uint64_t> generation = layers::AnimationInfo::GetGenerationFromFrame( |
1267 | 0 | GetPrimaryFrame(), record.mLayerType); |
1268 | 0 | // Unthrottle if the animation needs to be brought up to date |
1269 | 0 | if (!generation || effectSet->GetAnimationGeneration() != *generation) { |
1270 | 0 | return false; |
1271 | 0 | } |
1272 | 0 | |
1273 | 0 | // If this is a transform animation that affects the overflow region, |
1274 | 0 | // we should unthrottle the animation periodically. |
1275 | 0 | if (HasPropertiesThatMightAffectOverflow() && |
1276 | 0 | !CanThrottleOverflowChangesInScrollable(*frame)) { |
1277 | 0 | return false; |
1278 | 0 | } |
1279 | 0 | } |
1280 | 0 |
|
1281 | 0 | for (const AnimationProperty& property : mProperties) { |
1282 | 0 | if (!property.mIsRunningOnCompositor) { |
1283 | 0 | return false; |
1284 | 0 | } |
1285 | 0 | } |
1286 | 0 |
|
1287 | 0 | return true; |
1288 | 0 | } |
1289 | | |
1290 | | bool |
1291 | | KeyframeEffect::CanThrottleOverflowChanges(const nsIFrame& aFrame) const |
1292 | 0 | { |
1293 | 0 | TimeStamp now = aFrame.PresContext()->RefreshDriver()->MostRecentRefresh(); |
1294 | 0 |
|
1295 | 0 | EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, |
1296 | 0 | mTarget->mPseudoType); |
1297 | 0 | MOZ_ASSERT(effectSet, "CanOverflowTransformChanges is expected to be called" |
1298 | 0 | " on an effect in an effect set"); |
1299 | 0 | MOZ_ASSERT(mAnimation, "CanOverflowTransformChanges is expected to be called" |
1300 | 0 | " on an effect with a parent animation"); |
1301 | 0 | TimeStamp lastSyncTime = effectSet->LastOverflowAnimationSyncTime(); |
1302 | 0 | // If this animation can cause overflow, we can throttle some of the ticks. |
1303 | 0 | return (!lastSyncTime.IsNull() && |
1304 | 0 | (now - lastSyncTime) < OverflowRegionRefreshInterval()); |
1305 | 0 | } |
1306 | | |
1307 | | bool |
1308 | | KeyframeEffect::CanThrottleOverflowChangesInScrollable(nsIFrame& aFrame) const |
1309 | 0 | { |
1310 | 0 | // If the target element is not associated with any documents, we don't care |
1311 | 0 | // it. |
1312 | 0 | nsIDocument* doc = GetRenderedDocument(); |
1313 | 0 | if (!doc) { |
1314 | 0 | return true; |
1315 | 0 | } |
1316 | 0 | |
1317 | 0 | bool hasIntersectionObservers = doc->HasIntersectionObservers(); |
1318 | 0 |
|
1319 | 0 | // If we know that the animation cannot cause overflow, |
1320 | 0 | // we can just disable flushes for this animation. |
1321 | 0 |
|
1322 | 0 | // If we don't show scrollbars and have no intersection observers, we don't |
1323 | 0 | // care about overflow. |
1324 | 0 | if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0 && |
1325 | 0 | !hasIntersectionObservers) { |
1326 | 0 | return true; |
1327 | 0 | } |
1328 | 0 | |
1329 | 0 | if (CanThrottleOverflowChanges(aFrame)) { |
1330 | 0 | return true; |
1331 | 0 | } |
1332 | 0 | |
1333 | 0 | // If we have any intersection observers, we unthrottle this transform |
1334 | 0 | // animation periodically. |
1335 | 0 | if (hasIntersectionObservers) { |
1336 | 0 | return false; |
1337 | 0 | } |
1338 | 0 | |
1339 | 0 | // If the nearest scrollable ancestor has overflow:hidden, |
1340 | 0 | // we don't care about overflow. |
1341 | 0 | nsIScrollableFrame* scrollable = |
1342 | 0 | nsLayoutUtils::GetNearestScrollableFrame(&aFrame); |
1343 | 0 | if (!scrollable) { |
1344 | 0 | return true; |
1345 | 0 | } |
1346 | 0 | |
1347 | 0 | ScrollStyles ss = scrollable->GetScrollStyles(); |
1348 | 0 | if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN && |
1349 | 0 | ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && |
1350 | 0 | scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) { |
1351 | 0 | return true; |
1352 | 0 | } |
1353 | 0 | |
1354 | 0 | return false; |
1355 | 0 | } |
1356 | | |
1357 | | nsIFrame* |
1358 | | KeyframeEffect::GetStyleFrame() const |
1359 | 0 | { |
1360 | 0 | nsIFrame* frame = GetPrimaryFrame(); |
1361 | 0 | if (!frame) { |
1362 | 0 | return nullptr; |
1363 | 0 | } |
1364 | 0 | |
1365 | 0 | return nsLayoutUtils::GetStyleFrame(frame); |
1366 | 0 | } |
1367 | | |
1368 | | nsIFrame* |
1369 | | KeyframeEffect::GetPrimaryFrame() const |
1370 | 0 | { |
1371 | 0 | nsIFrame* frame = nullptr; |
1372 | 0 | if (!mTarget) { |
1373 | 0 | return frame; |
1374 | 0 | } |
1375 | 0 | |
1376 | 0 | if (mTarget->mPseudoType == CSSPseudoElementType::before) { |
1377 | 0 | frame = nsLayoutUtils::GetBeforeFrame(mTarget->mElement); |
1378 | 0 | } else if (mTarget->mPseudoType == CSSPseudoElementType::after) { |
1379 | 0 | frame = nsLayoutUtils::GetAfterFrame(mTarget->mElement); |
1380 | 0 | } else { |
1381 | 0 | frame = mTarget->mElement->GetPrimaryFrame(); |
1382 | 0 | MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo, |
1383 | 0 | "unknown mTarget->mPseudoType"); |
1384 | 0 | } |
1385 | 0 |
|
1386 | 0 | return frame; |
1387 | 0 | } |
1388 | | |
1389 | | nsIDocument* |
1390 | | KeyframeEffect::GetRenderedDocument() const |
1391 | 0 | { |
1392 | 0 | if (!mTarget) { |
1393 | 0 | return nullptr; |
1394 | 0 | } |
1395 | 0 | return mTarget->mElement->GetComposedDoc(); |
1396 | 0 | } |
1397 | | |
1398 | | nsIPresShell* |
1399 | | KeyframeEffect::GetPresShell() const |
1400 | 0 | { |
1401 | 0 | nsIDocument* doc = GetRenderedDocument(); |
1402 | 0 | if (!doc) { |
1403 | 0 | return nullptr; |
1404 | 0 | } |
1405 | 0 | return doc->GetShell(); |
1406 | 0 | } |
1407 | | |
1408 | | /* static */ bool |
1409 | | KeyframeEffect::IsGeometricProperty(const nsCSSPropertyID aProperty) |
1410 | 0 | { |
1411 | 0 | MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), |
1412 | 0 | "Property should be a longhand property"); |
1413 | 0 |
|
1414 | 0 | switch (aProperty) { |
1415 | 0 | case eCSSProperty_bottom: |
1416 | 0 | case eCSSProperty_height: |
1417 | 0 | case eCSSProperty_left: |
1418 | 0 | case eCSSProperty_margin_bottom: |
1419 | 0 | case eCSSProperty_margin_left: |
1420 | 0 | case eCSSProperty_margin_right: |
1421 | 0 | case eCSSProperty_margin_top: |
1422 | 0 | case eCSSProperty_padding_bottom: |
1423 | 0 | case eCSSProperty_padding_left: |
1424 | 0 | case eCSSProperty_padding_right: |
1425 | 0 | case eCSSProperty_padding_top: |
1426 | 0 | case eCSSProperty_right: |
1427 | 0 | case eCSSProperty_top: |
1428 | 0 | case eCSSProperty_width: |
1429 | 0 | return true; |
1430 | 0 | default: |
1431 | 0 | return false; |
1432 | 0 | } |
1433 | 0 | } |
1434 | | |
1435 | | /* static */ bool |
1436 | | KeyframeEffect::CanAnimateTransformOnCompositor( |
1437 | | const nsIFrame* aFrame, |
1438 | | AnimationPerformanceWarning::Type& aPerformanceWarning) |
1439 | 0 | { |
1440 | 0 | // Disallow OMTA for preserve-3d transform. Note that we check the style property |
1441 | 0 | // rather than Extend3DContext() since that can recurse back into this function |
1442 | 0 | // via HasOpacity(). See bug 779598. |
1443 | 0 | if (aFrame->Combines3DTransformWithAncestors() || |
1444 | 0 | aFrame->StyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D) { |
1445 | 0 | aPerformanceWarning = AnimationPerformanceWarning::Type::TransformPreserve3D; |
1446 | 0 | return false; |
1447 | 0 | } |
1448 | 0 | // Note that testing BackfaceIsHidden() is not a sufficient test for |
1449 | 0 | // what we need for animating backface-visibility correctly if we |
1450 | 0 | // remove the above test for Extend3DContext(); that would require |
1451 | 0 | // looking at backface-visibility on descendants as well. See bug 1186204. |
1452 | 0 | if (aFrame->BackfaceIsHidden()) { |
1453 | 0 | aPerformanceWarning = |
1454 | 0 | AnimationPerformanceWarning::Type::TransformBackfaceVisibilityHidden; |
1455 | 0 | return false; |
1456 | 0 | } |
1457 | 0 | // Async 'transform' animations of aFrames with SVG transforms is not |
1458 | 0 | // supported. See bug 779599. |
1459 | 0 | if (aFrame->IsSVGTransformed()) { |
1460 | 0 | aPerformanceWarning = AnimationPerformanceWarning::Type::TransformSVG; |
1461 | 0 | return false; |
1462 | 0 | } |
1463 | 0 | |
1464 | 0 | return true; |
1465 | 0 | } |
1466 | | |
1467 | | bool |
1468 | | KeyframeEffect::ShouldBlockAsyncTransformAnimations( |
1469 | | const nsIFrame* aFrame, |
1470 | | AnimationPerformanceWarning::Type& aPerformanceWarning) const |
1471 | 0 | { |
1472 | 0 | EffectSet* effectSet = |
1473 | 0 | EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); |
1474 | 0 | for (const AnimationProperty& property : mProperties) { |
1475 | 0 | // If there is a property for animations level that is overridden by |
1476 | 0 | // !important rules, it should not block other animations from running |
1477 | 0 | // on the compositor. |
1478 | 0 | // NOTE: We don't currently check for !important rules for properties that |
1479 | 0 | // don't run on the compositor. As result such properties (e.g. margin-left) |
1480 | 0 | // can still block async animations even if they are overridden by |
1481 | 0 | // !important rules. |
1482 | 0 | if (effectSet && |
1483 | 0 | effectSet->PropertiesWithImportantRules() |
1484 | 0 | .HasProperty(property.mProperty) && |
1485 | 0 | effectSet->PropertiesForAnimationsLevel() |
1486 | 0 | .HasProperty(property.mProperty)) { |
1487 | 0 | continue; |
1488 | 0 | } |
1489 | 0 | // Check for geometric properties |
1490 | 0 | if (IsGeometricProperty(property.mProperty)) { |
1491 | 0 | aPerformanceWarning = |
1492 | 0 | AnimationPerformanceWarning::Type::TransformWithGeometricProperties; |
1493 | 0 | return true; |
1494 | 0 | } |
1495 | 0 | |
1496 | 0 | // Check for unsupported transform animations |
1497 | 0 | if (property.mProperty == eCSSProperty_transform) { |
1498 | 0 | if (!CanAnimateTransformOnCompositor(aFrame, |
1499 | 0 | aPerformanceWarning)) { |
1500 | 0 | return true; |
1501 | 0 | } |
1502 | 0 | } |
1503 | 0 | } |
1504 | 0 |
|
1505 | 0 | // XXX cku temporarily disable async-animation when this frame has any |
1506 | 0 | // individual transforms before bug 1425837 been fixed. |
1507 | 0 | if (aFrame->StyleDisplay()->HasIndividualTransform()) { |
1508 | 0 | return true; |
1509 | 0 | } |
1510 | 0 | |
1511 | 0 | return false; |
1512 | 0 | } |
1513 | | |
1514 | | bool |
1515 | | KeyframeEffect::HasGeometricProperties() const |
1516 | 0 | { |
1517 | 0 | for (const AnimationProperty& property : mProperties) { |
1518 | 0 | if (IsGeometricProperty(property.mProperty)) { |
1519 | 0 | return true; |
1520 | 0 | } |
1521 | 0 | } |
1522 | 0 |
|
1523 | 0 | return false; |
1524 | 0 | } |
1525 | | |
1526 | | void |
1527 | | KeyframeEffect::SetPerformanceWarning( |
1528 | | nsCSSPropertyID aProperty, |
1529 | | const AnimationPerformanceWarning& aWarning) |
1530 | 0 | { |
1531 | 0 | for (AnimationProperty& property : mProperties) { |
1532 | 0 | if (property.mProperty == aProperty && |
1533 | 0 | (!property.mPerformanceWarning || |
1534 | 0 | *property.mPerformanceWarning != aWarning)) { |
1535 | 0 | property.mPerformanceWarning = Some(aWarning); |
1536 | 0 |
|
1537 | 0 | nsAutoString localizedString; |
1538 | 0 | if (nsLayoutUtils::IsAnimationLoggingEnabled() && |
1539 | 0 | property.mPerformanceWarning->ToLocalizedString(localizedString)) { |
1540 | 0 | nsAutoCString logMessage = NS_ConvertUTF16toUTF8(localizedString); |
1541 | 0 | AnimationUtils::LogAsyncAnimationFailure(logMessage, mTarget->mElement); |
1542 | 0 | } |
1543 | 0 | return; |
1544 | 0 | } |
1545 | 0 | } |
1546 | 0 | } |
1547 | | |
1548 | | |
1549 | | already_AddRefed<ComputedStyle> |
1550 | | KeyframeEffect::CreateComputedStyleForAnimationValue( |
1551 | | nsCSSPropertyID aProperty, |
1552 | | const AnimationValue& aValue, |
1553 | | nsPresContext* aPresContext, |
1554 | | const ComputedStyle* aBaseComputedStyle) |
1555 | 0 | { |
1556 | 0 | MOZ_ASSERT(aBaseComputedStyle, |
1557 | 0 | "CreateComputedStyleForAnimationValue needs to be called " |
1558 | 0 | "with a valid ComputedStyle"); |
1559 | 0 |
|
1560 | 0 | ServoStyleSet* styleSet = aPresContext->StyleSet(); |
1561 | 0 | Element* elementForResolve = |
1562 | 0 | EffectCompositor::GetElementToRestyle(mTarget->mElement, |
1563 | 0 | mTarget->mPseudoType); |
1564 | 0 | MOZ_ASSERT(elementForResolve, "The target element shouldn't be null"); |
1565 | 0 | return styleSet->ResolveServoStyleByAddingAnimation(elementForResolve, |
1566 | 0 | aBaseComputedStyle, |
1567 | 0 | aValue.mServo); |
1568 | 0 | } |
1569 | | |
1570 | | void |
1571 | | KeyframeEffect::CalculateCumulativeChangeHint(const ComputedStyle* aComputedStyle) |
1572 | 0 | { |
1573 | 0 | mCumulativeChangeHint = nsChangeHint(0); |
1574 | 0 |
|
1575 | 0 | nsPresContext* presContext = |
1576 | 0 | nsContentUtils::GetContextForContent(mTarget->mElement); |
1577 | 0 | if (!presContext) { |
1578 | 0 | // Change hints make no sense if we're not rendered. |
1579 | 0 | // |
1580 | 0 | // Actually, we cannot even post them anywhere. |
1581 | 0 | return; |
1582 | 0 | } |
1583 | 0 | |
1584 | 0 | for (const AnimationProperty& property : mProperties) { |
1585 | 0 | // For opacity property we don't produce any change hints that are not |
1586 | 0 | // included in nsChangeHint_Hints_CanIgnoreIfNotVisible so we can throttle |
1587 | 0 | // opacity animations regardless of the change they produce. This |
1588 | 0 | // optimization is particularly important since it allows us to throttle |
1589 | 0 | // opacity animations with missing 0%/100% keyframes. |
1590 | 0 | if (property.mProperty == eCSSProperty_opacity) { |
1591 | 0 | continue; |
1592 | 0 | } |
1593 | 0 | |
1594 | 0 | for (const AnimationPropertySegment& segment : property.mSegments) { |
1595 | 0 | // In case composite operation is not 'replace' or value is null, |
1596 | 0 | // we can't throttle animations which will not cause any layout changes |
1597 | 0 | // on invisible elements because we can't calculate the change hint for |
1598 | 0 | // such properties until we compose it. |
1599 | 0 | if (!segment.HasReplaceableValues()) { |
1600 | 0 | if (property.mProperty != eCSSProperty_transform) { |
1601 | 0 | mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible; |
1602 | 0 | return; |
1603 | 0 | } |
1604 | 0 | // We try a little harder to optimize transform animations simply |
1605 | 0 | // because they are so common (the second-most commonly animated |
1606 | 0 | // property at the time of writing). So if we encounter a transform |
1607 | 0 | // segment that needs composing with the underlying value, we just add |
1608 | 0 | // all the change hints a transform animation is known to be able to |
1609 | 0 | // generate. |
1610 | 0 | mCumulativeChangeHint |= |
1611 | 0 | nsChangeHint_ComprehensiveAddOrRemoveTransform | |
1612 | 0 | nsChangeHint_UpdatePostTransformOverflow | |
1613 | 0 | nsChangeHint_UpdateTransformLayer; |
1614 | 0 | continue; |
1615 | 0 | } |
1616 | 0 |
|
1617 | 0 | RefPtr<ComputedStyle> fromContext = |
1618 | 0 | CreateComputedStyleForAnimationValue(property.mProperty, |
1619 | 0 | segment.mFromValue, |
1620 | 0 | presContext, |
1621 | 0 | aComputedStyle); |
1622 | 0 | if (!fromContext) { |
1623 | 0 | mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible; |
1624 | 0 | return; |
1625 | 0 | } |
1626 | 0 |
|
1627 | 0 | RefPtr<ComputedStyle> toContext = |
1628 | 0 | CreateComputedStyleForAnimationValue(property.mProperty, |
1629 | 0 | segment.mToValue, |
1630 | 0 | presContext, |
1631 | 0 | aComputedStyle); |
1632 | 0 | if (!toContext) { |
1633 | 0 | mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible; |
1634 | 0 | return; |
1635 | 0 | } |
1636 | 0 |
|
1637 | 0 | uint32_t equalStructs = 0; |
1638 | 0 | nsChangeHint changeHint = |
1639 | 0 | fromContext->CalcStyleDifference(toContext, &equalStructs); |
1640 | 0 |
|
1641 | 0 | mCumulativeChangeHint |= changeHint; |
1642 | 0 | } |
1643 | 0 | } |
1644 | 0 | } |
1645 | | |
1646 | | void |
1647 | | KeyframeEffect::SetAnimation(Animation* aAnimation) |
1648 | 0 | { |
1649 | 0 | if (mAnimation == aAnimation) { |
1650 | 0 | return; |
1651 | 0 | } |
1652 | 0 | |
1653 | 0 | // Restyle for the old animation. |
1654 | 0 | RequestRestyle(EffectCompositor::RestyleType::Layer); |
1655 | 0 |
|
1656 | 0 | mAnimation = aAnimation; |
1657 | 0 |
|
1658 | 0 | // The order of these function calls is important: |
1659 | 0 | // NotifyAnimationTimingUpdated() need the updated mIsRelevant flag to check |
1660 | 0 | // if it should create the effectSet or not, and MarkCascadeNeedsUpdate() |
1661 | 0 | // needs a valid effectSet, so we should call them in this order. |
1662 | 0 | if (mAnimation) { |
1663 | 0 | mAnimation->UpdateRelevance(); |
1664 | 0 | } |
1665 | 0 | NotifyAnimationTimingUpdated(); |
1666 | 0 | if (mAnimation) { |
1667 | 0 | MarkCascadeNeedsUpdate(); |
1668 | 0 | } |
1669 | 0 | } |
1670 | | |
1671 | | bool |
1672 | | KeyframeEffect::CanIgnoreIfNotVisible() const |
1673 | 0 | { |
1674 | 0 | if (!AnimationUtils::IsOffscreenThrottlingEnabled()) { |
1675 | 0 | return false; |
1676 | 0 | } |
1677 | 0 | |
1678 | 0 | // FIXME: For further sophisticated optimization we need to check |
1679 | 0 | // change hint on the segment corresponding to computedTiming.progress. |
1680 | 0 | return NS_IsHintSubset( |
1681 | 0 | mCumulativeChangeHint, nsChangeHint_Hints_CanIgnoreIfNotVisible); |
1682 | 0 | } |
1683 | | |
1684 | | void |
1685 | | KeyframeEffect::MaybeUpdateFrameForCompositor() |
1686 | 0 | { |
1687 | 0 | nsIFrame* frame = GetStyleFrame(); |
1688 | 0 | if (!frame) { |
1689 | 0 | return; |
1690 | 0 | } |
1691 | 0 | |
1692 | 0 | // FIXME: Bug 1272495: If this effect does not win in the cascade, the |
1693 | 0 | // NS_FRAME_MAY_BE_TRANSFORMED flag should be removed when the animation |
1694 | 0 | // will be removed from effect set or the transform keyframes are removed |
1695 | 0 | // by setKeyframes. The latter case will be hard to solve though. |
1696 | 0 | for (const AnimationProperty& property : mProperties) { |
1697 | 0 | if (property.mProperty == eCSSProperty_transform) { |
1698 | 0 | frame->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED); |
1699 | 0 | return; |
1700 | 0 | } |
1701 | 0 | } |
1702 | 0 | } |
1703 | | |
1704 | | void |
1705 | | KeyframeEffect::MarkCascadeNeedsUpdate() |
1706 | 0 | { |
1707 | 0 | if (!mTarget) { |
1708 | 0 | return; |
1709 | 0 | } |
1710 | 0 | |
1711 | 0 | EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, |
1712 | 0 | mTarget->mPseudoType); |
1713 | 0 | if (!effectSet) { |
1714 | 0 | return; |
1715 | 0 | } |
1716 | 0 | effectSet->MarkCascadeNeedsUpdate(); |
1717 | 0 | } |
1718 | | |
1719 | | /* static */ bool |
1720 | | KeyframeEffect::HasComputedTimingChanged( |
1721 | | const ComputedTiming& aComputedTiming, |
1722 | | IterationCompositeOperation aIterationComposite, |
1723 | | const Nullable<double>& aProgressOnLastCompose, |
1724 | | uint64_t aCurrentIterationOnLastCompose) |
1725 | 0 | { |
1726 | 0 | // Typically we don't need to request a restyle if the progress hasn't |
1727 | 0 | // changed since the last call to ComposeStyle. The one exception is if the |
1728 | 0 | // iteration composite mode is 'accumulate' and the current iteration has |
1729 | 0 | // changed, since that will often produce a different result. |
1730 | 0 | return aComputedTiming.mProgress != aProgressOnLastCompose || |
1731 | 0 | (aIterationComposite == IterationCompositeOperation::Accumulate && |
1732 | 0 | aComputedTiming.mCurrentIteration != aCurrentIterationOnLastCompose); |
1733 | 0 | } |
1734 | | |
1735 | | bool |
1736 | | KeyframeEffect::HasComputedTimingChanged() const |
1737 | 0 | { |
1738 | 0 | ComputedTiming computedTiming = GetComputedTiming(); |
1739 | 0 | return HasComputedTimingChanged(computedTiming, |
1740 | 0 | mEffectOptions.mIterationComposite, |
1741 | 0 | mProgressOnLastCompose, |
1742 | 0 | mCurrentIterationOnLastCompose); |
1743 | 0 | } |
1744 | | |
1745 | | bool |
1746 | | KeyframeEffect::ContainsAnimatedScale(const nsIFrame* aFrame) const |
1747 | 0 | { |
1748 | 0 | if (!IsCurrent()) { |
1749 | 0 | return false; |
1750 | 0 | } |
1751 | 0 | |
1752 | 0 | for (const AnimationProperty& prop : mProperties) { |
1753 | 0 | if (prop.mProperty != eCSSProperty_transform) { |
1754 | 0 | continue; |
1755 | 0 | } |
1756 | 0 | |
1757 | 0 | AnimationValue baseStyle = BaseStyle(prop.mProperty); |
1758 | 0 | if (baseStyle.IsNull()) { |
1759 | 0 | // If we failed to get the base style, we consider it has scale value |
1760 | 0 | // here just to be safe. |
1761 | 0 | return true; |
1762 | 0 | } |
1763 | 0 | gfx::Size size = baseStyle.GetScaleValue(aFrame); |
1764 | 0 | if (size != gfx::Size(1.0f, 1.0f)) { |
1765 | 0 | return true; |
1766 | 0 | } |
1767 | 0 | |
1768 | 0 | // This is actually overestimate because there are some cases that combining |
1769 | 0 | // the base value and from/to value produces 1:1 scale. But it doesn't |
1770 | 0 | // really matter. |
1771 | 0 | for (const AnimationPropertySegment& segment : prop.mSegments) { |
1772 | 0 | if (!segment.mFromValue.IsNull()) { |
1773 | 0 | gfx::Size from = segment.mFromValue.GetScaleValue(aFrame); |
1774 | 0 | if (from != gfx::Size(1.0f, 1.0f)) { |
1775 | 0 | return true; |
1776 | 0 | } |
1777 | 0 | } |
1778 | 0 | if (!segment.mToValue.IsNull()) { |
1779 | 0 | gfx::Size to = segment.mToValue.GetScaleValue(aFrame); |
1780 | 0 | if (to != gfx::Size(1.0f, 1.0f)) { |
1781 | 0 | return true; |
1782 | 0 | } |
1783 | 0 | } |
1784 | 0 | } |
1785 | 0 | } |
1786 | 0 |
|
1787 | 0 | return false; |
1788 | 0 | } |
1789 | | |
1790 | | void |
1791 | | KeyframeEffect::UpdateEffectSet(EffectSet* aEffectSet) const |
1792 | 0 | { |
1793 | 0 | if (!mInEffectSet) { |
1794 | 0 | return; |
1795 | 0 | } |
1796 | 0 | |
1797 | 0 | EffectSet* effectSet = |
1798 | 0 | aEffectSet ? aEffectSet |
1799 | 0 | : EffectSet::GetEffectSet(mTarget->mElement, |
1800 | 0 | mTarget->mPseudoType); |
1801 | 0 | if (!effectSet) { |
1802 | 0 | return; |
1803 | 0 | } |
1804 | 0 | |
1805 | 0 | nsIFrame* frame = GetStyleFrame(); |
1806 | 0 | if (HasAnimationOfProperty(eCSSProperty_opacity)) { |
1807 | 0 | effectSet->SetMayHaveOpacityAnimation(); |
1808 | 0 | EnumerateContinuationsOrIBSplitSiblings(frame, |
1809 | 0 | [](nsIFrame* aFrame) { |
1810 | 0 | aFrame->SetMayHaveOpacityAnimation(); |
1811 | 0 | } |
1812 | 0 | ); |
1813 | 0 | } |
1814 | 0 | if (HasAnimationOfProperty(eCSSProperty_transform)) { |
1815 | 0 | effectSet->SetMayHaveTransformAnimation(); |
1816 | 0 | EnumerateContinuationsOrIBSplitSiblings(frame, |
1817 | 0 | [](nsIFrame* aFrame) { |
1818 | 0 | aFrame->SetMayHaveTransformAnimation(); |
1819 | 0 | } |
1820 | 0 | ); |
1821 | 0 | } |
1822 | 0 | } |
1823 | | |
1824 | | } // namespace dom |
1825 | | } // namespace mozilla |