/src/mozilla-central/dom/animation/EffectCompositor.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 |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "EffectCompositor.h" |
8 | | |
9 | | #include <bitset> |
10 | | #include <initializer_list> |
11 | | |
12 | | #include "mozilla/dom/Animation.h" |
13 | | #include "mozilla/dom/Element.h" |
14 | | #include "mozilla/dom/KeyframeEffect.h" |
15 | | #include "mozilla/AnimationComparator.h" |
16 | | #include "mozilla/AnimationPerformanceWarning.h" |
17 | | #include "mozilla/AnimationTarget.h" |
18 | | #include "mozilla/AnimationUtils.h" |
19 | | #include "mozilla/AutoRestore.h" |
20 | | #include "mozilla/ComputedStyleInlines.h" |
21 | | #include "mozilla/EffectSet.h" |
22 | | #include "mozilla/LayerAnimationInfo.h" |
23 | | #include "mozilla/RestyleManager.h" |
24 | | #include "mozilla/ServoBindings.h" // Servo_GetProperties_Overriding_Animation |
25 | | #include "mozilla/ServoStyleSet.h" |
26 | | #include "mozilla/StyleAnimationValue.h" |
27 | | #include "mozilla/TypeTraits.h" // For std::forward<> |
28 | | #include "nsContentUtils.h" |
29 | | #include "nsCSSPseudoElements.h" |
30 | | #include "nsCSSPropertyIDSet.h" |
31 | | #include "nsCSSProps.h" |
32 | | #include "nsAtom.h" |
33 | | #include "nsIPresShell.h" |
34 | | #include "nsIPresShellInlines.h" |
35 | | #include "nsLayoutUtils.h" |
36 | | #include "nsTArray.h" |
37 | | #include "PendingAnimationTracker.h" |
38 | | |
39 | | using mozilla::dom::Animation; |
40 | | using mozilla::dom::Element; |
41 | | using mozilla::dom::KeyframeEffect; |
42 | | |
43 | | namespace mozilla { |
44 | | |
45 | | NS_IMPL_CYCLE_COLLECTION_CLASS(EffectCompositor) |
46 | | |
47 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EffectCompositor) |
48 | 0 | for (auto& elementSet : tmp->mElementsToRestyle) { |
49 | 0 | elementSet.Clear(); |
50 | 0 | } |
51 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
52 | | |
53 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EffectCompositor) |
54 | 0 | for (auto& elementSet : tmp->mElementsToRestyle) { |
55 | 0 | for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { |
56 | 0 | CycleCollectionNoteChild(cb, iter.Key().mElement, |
57 | 0 | "EffectCompositor::mElementsToRestyle[]", |
58 | 0 | cb.Flags()); |
59 | 0 | } |
60 | 0 | } |
61 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
62 | | |
63 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(EffectCompositor, AddRef) |
64 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(EffectCompositor, Release) |
65 | | |
66 | | namespace { |
67 | | enum class MatchForCompositor { |
68 | | // This animation matches and should run on the compositor if possible. |
69 | | Yes, |
70 | | // This (not currently playing) animation matches and can be run on the |
71 | | // compositor if there are other animations for this property that return |
72 | | // 'Yes'. |
73 | | IfNeeded, |
74 | | // This animation does not match or can't be run on the compositor. |
75 | | No, |
76 | | // This animation does not match or can't be run on the compositor and, |
77 | | // furthermore, its presence means we should not run any animations for this |
78 | | // property on the compositor. |
79 | | NoAndBlockThisProperty |
80 | | }; |
81 | | } |
82 | | |
83 | | static MatchForCompositor |
84 | | IsMatchForCompositor(const KeyframeEffect& aEffect, |
85 | | nsCSSPropertyID aProperty, |
86 | | const nsIFrame* aFrame) |
87 | 0 | { |
88 | 0 | const Animation* animation = aEffect.GetAnimation(); |
89 | 0 | MOZ_ASSERT(animation); |
90 | 0 |
|
91 | 0 | if (!animation->IsRelevant()) { |
92 | 0 | return MatchForCompositor::No; |
93 | 0 | } |
94 | 0 | |
95 | 0 | AnimationPerformanceWarning::Type warningType; |
96 | 0 | if (animation->ShouldBeSynchronizedWithMainThread(aProperty, aFrame, |
97 | 0 | warningType)) { |
98 | 0 | EffectCompositor::SetPerformanceWarning( |
99 | 0 | aFrame, aProperty, |
100 | 0 | AnimationPerformanceWarning(warningType)); |
101 | 0 | // For a given |aFrame|, we don't want some animations of |aProperty| to |
102 | 0 | // run on the compositor and others to run on the main thread, so if any |
103 | 0 | // need to be synchronized with the main thread, run them all there. |
104 | 0 | return MatchForCompositor::NoAndBlockThisProperty; |
105 | 0 | } |
106 | 0 | |
107 | 0 | if (!aEffect.HasEffectiveAnimationOfProperty(aProperty)) { |
108 | 0 | return MatchForCompositor::No; |
109 | 0 | } |
110 | 0 | |
111 | 0 | // If we know that the animation is not visible, we don't need to send the |
112 | 0 | // animation to the compositor. |
113 | 0 | if (!aFrame->IsVisibleOrMayHaveVisibleDescendants() || |
114 | 0 | aFrame->IsScrolledOutOfView()) { |
115 | 0 | return MatchForCompositor::NoAndBlockThisProperty; |
116 | 0 | } |
117 | 0 | |
118 | 0 | return animation->IsPlaying() |
119 | 0 | ? MatchForCompositor::Yes |
120 | 0 | : MatchForCompositor::IfNeeded; |
121 | 0 | } |
122 | | |
123 | | // Helper function to factor out the common logic from |
124 | | // GetAnimationsForCompositor and HasAnimationsForCompositor. |
125 | | // |
126 | | // Takes an optional array to fill with eligible animations. |
127 | | // |
128 | | // Returns true if there are eligible animations, false otherwise. |
129 | | bool |
130 | | FindAnimationsForCompositor(const nsIFrame* aFrame, |
131 | | nsCSSPropertyID aProperty, |
132 | | nsTArray<RefPtr<dom::Animation>>* aMatches /*out*/) |
133 | 0 | { |
134 | 0 | MOZ_ASSERT(!aMatches || aMatches->IsEmpty(), |
135 | 0 | "Matches array, if provided, should be empty"); |
136 | 0 |
|
137 | 0 | EffectSet* effects = EffectSet::GetEffectSet(aFrame); |
138 | 0 | if (!effects || effects->IsEmpty()) { |
139 | 0 | return false; |
140 | 0 | } |
141 | 0 | |
142 | 0 | // First check for newly-started transform animations that should be |
143 | 0 | // synchronized with geometric animations. We need to do this before any |
144 | 0 | // other early returns (the one above is ok) since we can only check this |
145 | 0 | // state when the animation is newly-started. |
146 | 0 | if (aProperty == eCSSProperty_transform) { |
147 | 0 | PendingAnimationTracker* tracker = |
148 | 0 | aFrame->PresContext()->Document()->GetPendingAnimationTracker(); |
149 | 0 | if (tracker) { |
150 | 0 | tracker->MarkAnimationsThatMightNeedSynchronization(); |
151 | 0 | } |
152 | 0 | } |
153 | 0 |
|
154 | 0 | // If the property will be added to the animations level of the cascade but |
155 | 0 | // there is an !important rule for that property in the cascade then the |
156 | 0 | // animation will not be applied since the !important rule overrides it. |
157 | 0 | if (effects->PropertiesWithImportantRules().HasProperty(aProperty) && |
158 | 0 | effects->PropertiesForAnimationsLevel().HasProperty(aProperty)) { |
159 | 0 | return false; |
160 | 0 | } |
161 | 0 | |
162 | 0 | if (aFrame->RefusedAsyncAnimation()) { |
163 | 0 | return false; |
164 | 0 | } |
165 | 0 | |
166 | 0 | // The animation cascade will almost always be up-to-date by this point |
167 | 0 | // but there are some cases such as when we are restoring the refresh driver |
168 | 0 | // from test control after seeking where it might not be the case. |
169 | 0 | // |
170 | 0 | // Those cases are probably not important but just to be safe, let's make |
171 | 0 | // sure the cascade is up to date since if it *is* up to date, this is |
172 | 0 | // basically a no-op. |
173 | 0 | Maybe<NonOwningAnimationTarget> pseudoElement = |
174 | 0 | EffectCompositor::GetAnimationElementAndPseudoForFrame(aFrame); |
175 | 0 | MOZ_ASSERT(pseudoElement, |
176 | 0 | "We have a valid element for the frame, if we don't we should " |
177 | 0 | "have bailed out at above the call to EffectSet::GetEffectSet"); |
178 | 0 | EffectCompositor::MaybeUpdateCascadeResults(pseudoElement->mElement, |
179 | 0 | pseudoElement->mPseudoType); |
180 | 0 |
|
181 | 0 | if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) { |
182 | 0 | if (nsLayoutUtils::IsAnimationLoggingEnabled()) { |
183 | 0 | nsCString message; |
184 | 0 | message.AppendLiteral("Performance warning: Async animations are " |
185 | 0 | "disabled"); |
186 | 0 | AnimationUtils::LogAsyncAnimationFailure(message); |
187 | 0 | } |
188 | 0 | return false; |
189 | 0 | } |
190 | 0 |
|
191 | 0 | // Disable async animations if we have a rendering observer that |
192 | 0 | // depends on our content (svg masking, -moz-element etc) so that |
193 | 0 | // it gets updated correctly. |
194 | 0 | nsIContent* content = aFrame->GetContent(); |
195 | 0 | while (content) { |
196 | 0 | if (content->HasRenderingObservers()) { |
197 | 0 | EffectCompositor::SetPerformanceWarning( |
198 | 0 | aFrame, aProperty, |
199 | 0 | AnimationPerformanceWarning( |
200 | 0 | AnimationPerformanceWarning::Type::HasRenderingObserver)); |
201 | 0 | return false; |
202 | 0 | } |
203 | 0 | content = content->GetParent(); |
204 | 0 | } |
205 | 0 |
|
206 | 0 | bool foundRunningAnimations = false; |
207 | 0 | for (KeyframeEffect* effect : *effects) { |
208 | 0 | MatchForCompositor matchResult = |
209 | 0 | IsMatchForCompositor(*effect, aProperty, aFrame); |
210 | 0 |
|
211 | 0 | if (matchResult == MatchForCompositor::NoAndBlockThisProperty) { |
212 | 0 | // For a given |aFrame|, we don't want some animations of |aProperty| to |
213 | 0 | // run on the compositor and others to run on the main thread, so if any |
214 | 0 | // need to be synchronized with the main thread, run them all there. |
215 | 0 | if (aMatches) { |
216 | 0 | aMatches->Clear(); |
217 | 0 | } |
218 | 0 | return false; |
219 | 0 | } |
220 | 0 |
|
221 | 0 | if (matchResult == MatchForCompositor::No) { |
222 | 0 | continue; |
223 | 0 | } |
224 | 0 | |
225 | 0 | if (aMatches) { |
226 | 0 | aMatches->AppendElement(effect->GetAnimation()); |
227 | 0 | } |
228 | 0 |
|
229 | 0 | if (matchResult == MatchForCompositor::Yes) { |
230 | 0 | foundRunningAnimations = true; |
231 | 0 | } |
232 | 0 | } |
233 | 0 |
|
234 | 0 | // If all animations we added were not currently playing animations, don't |
235 | 0 | // send them to the compositor. |
236 | 0 | if (aMatches && !foundRunningAnimations) { |
237 | 0 | aMatches->Clear(); |
238 | 0 | } |
239 | 0 |
|
240 | 0 | MOZ_ASSERT(!foundRunningAnimations || !aMatches || !aMatches->IsEmpty(), |
241 | 0 | "If return value is true, matches array should be non-empty"); |
242 | 0 |
|
243 | 0 | if (aMatches && foundRunningAnimations) { |
244 | 0 | aMatches->Sort(AnimationPtrComparator<RefPtr<dom::Animation>>()); |
245 | 0 | } |
246 | 0 |
|
247 | 0 | return foundRunningAnimations; |
248 | 0 | } |
249 | | |
250 | | void |
251 | | EffectCompositor::RequestRestyle(dom::Element* aElement, |
252 | | CSSPseudoElementType aPseudoType, |
253 | | RestyleType aRestyleType, |
254 | | CascadeLevel aCascadeLevel) |
255 | 0 | { |
256 | 0 | if (!mPresContext) { |
257 | 0 | // Pres context will be null after the effect compositor is disconnected. |
258 | 0 | return; |
259 | 0 | } |
260 | 0 | |
261 | 0 | // Ignore animations on orphaned elements and elements in documents without |
262 | 0 | // a pres shell (e.g. XMLHttpRequest responseXML documents). |
263 | 0 | if (!nsContentUtils::GetPresShellForContent(aElement)) { |
264 | 0 | return; |
265 | 0 | } |
266 | 0 | |
267 | 0 | auto& elementsToRestyle = mElementsToRestyle[aCascadeLevel]; |
268 | 0 | PseudoElementHashEntry::KeyType key = { aElement, aPseudoType }; |
269 | 0 |
|
270 | 0 | if (aRestyleType == RestyleType::Throttled) { |
271 | 0 | elementsToRestyle.LookupForAdd(key).OrInsert([]() { return false; }); |
272 | 0 | mPresContext->PresShell()->SetNeedThrottledAnimationFlush(); |
273 | 0 | } else { |
274 | 0 | bool skipRestyle; |
275 | 0 | // Update hashtable first in case PostRestyleForAnimation mutates it. |
276 | 0 | // (It shouldn't, but just to be sure.) |
277 | 0 | if (auto p = elementsToRestyle.LookupForAdd(key)) { |
278 | 0 | skipRestyle = p.Data(); |
279 | 0 | p.Data() = true; |
280 | 0 | } else { |
281 | 0 | skipRestyle = false; |
282 | 0 | p.OrInsert([]() { return true; }); |
283 | 0 | } |
284 | 0 |
|
285 | 0 | if (!skipRestyle) { |
286 | 0 | PostRestyleForAnimation(aElement, aPseudoType, aCascadeLevel); |
287 | 0 | } |
288 | 0 | } |
289 | 0 |
|
290 | 0 | if (aRestyleType == RestyleType::Layer) { |
291 | 0 | mPresContext->RestyleManager()->IncrementAnimationGeneration(); |
292 | 0 | EffectSet* effectSet = |
293 | 0 | EffectSet::GetEffectSet(aElement, aPseudoType); |
294 | 0 | if (effectSet) { |
295 | 0 | effectSet->UpdateAnimationGeneration(mPresContext); |
296 | 0 | } |
297 | 0 | } |
298 | 0 | } |
299 | | |
300 | | void |
301 | | EffectCompositor::PostRestyleForAnimation(dom::Element* aElement, |
302 | | CSSPseudoElementType aPseudoType, |
303 | | CascadeLevel aCascadeLevel) |
304 | 0 | { |
305 | 0 | if (!mPresContext) { |
306 | 0 | return; |
307 | 0 | } |
308 | 0 | |
309 | 0 | dom::Element* element = GetElementToRestyle(aElement, aPseudoType); |
310 | 0 | if (!element) { |
311 | 0 | return; |
312 | 0 | } |
313 | 0 | |
314 | 0 | nsRestyleHint hint = aCascadeLevel == CascadeLevel::Transitions ? |
315 | 0 | eRestyle_CSSTransitions : |
316 | 0 | eRestyle_CSSAnimations; |
317 | 0 |
|
318 | 0 | MOZ_ASSERT(NS_IsMainThread(), |
319 | 0 | "Restyle request during restyling should be requested only on " |
320 | 0 | "the main-thread. e.g. after the parallel traversal"); |
321 | 0 | if (ServoStyleSet::IsInServoTraversal() || mIsInPreTraverse) { |
322 | 0 | MOZ_ASSERT(hint == eRestyle_CSSAnimations || |
323 | 0 | hint == eRestyle_CSSTransitions); |
324 | 0 |
|
325 | 0 | // We can't call Servo_NoteExplicitHints here since AtomicRefCell does not |
326 | 0 | // allow us mutate ElementData of the |aElement| in SequentialTask. |
327 | 0 | // Instead we call Servo_NoteExplicitHints for the element in PreTraverse() |
328 | 0 | // which will be called right before the second traversal that we do for |
329 | 0 | // updating CSS animations. |
330 | 0 | // In that case PreTraverse() will return true so that we know to do the |
331 | 0 | // second traversal so we don't need to post any restyle requests to the |
332 | 0 | // PresShell. |
333 | 0 | return; |
334 | 0 | } |
335 | 0 |
|
336 | 0 | MOZ_ASSERT(!mPresContext->RestyleManager()->IsInStyleRefresh()); |
337 | 0 |
|
338 | 0 | mPresContext->PresShell()->RestyleForAnimation(element, hint); |
339 | 0 | } |
340 | | |
341 | | void |
342 | | EffectCompositor::PostRestyleForThrottledAnimations() |
343 | 0 | { |
344 | 0 | for (size_t i = 0; i < kCascadeLevelCount; i++) { |
345 | 0 | CascadeLevel cascadeLevel = CascadeLevel(i); |
346 | 0 | auto& elementSet = mElementsToRestyle[cascadeLevel]; |
347 | 0 |
|
348 | 0 | for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { |
349 | 0 | bool& postedRestyle = iter.Data(); |
350 | 0 | if (postedRestyle) { |
351 | 0 | continue; |
352 | 0 | } |
353 | 0 | |
354 | 0 | PostRestyleForAnimation(iter.Key().mElement, |
355 | 0 | iter.Key().mPseudoType, |
356 | 0 | cascadeLevel); |
357 | 0 | postedRestyle = true; |
358 | 0 | } |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | | void |
363 | | EffectCompositor::ClearRestyleRequestsFor(Element* aElement) |
364 | 0 | { |
365 | 0 | MOZ_ASSERT(aElement); |
366 | 0 |
|
367 | 0 | auto& elementsToRestyle = mElementsToRestyle[CascadeLevel::Animations]; |
368 | 0 |
|
369 | 0 | CSSPseudoElementType pseudoType = aElement->GetPseudoElementType(); |
370 | 0 | if (pseudoType == CSSPseudoElementType::NotPseudo) { |
371 | 0 | PseudoElementHashEntry::KeyType notPseudoKey = |
372 | 0 | { aElement, CSSPseudoElementType::NotPseudo }; |
373 | 0 | PseudoElementHashEntry::KeyType beforePseudoKey = |
374 | 0 | { aElement, CSSPseudoElementType::before }; |
375 | 0 | PseudoElementHashEntry::KeyType afterPseudoKey = |
376 | 0 | { aElement, CSSPseudoElementType::after }; |
377 | 0 |
|
378 | 0 | elementsToRestyle.Remove(notPseudoKey); |
379 | 0 | elementsToRestyle.Remove(beforePseudoKey); |
380 | 0 | elementsToRestyle.Remove(afterPseudoKey); |
381 | 0 | } else if (pseudoType == CSSPseudoElementType::before || |
382 | 0 | pseudoType == CSSPseudoElementType::after) { |
383 | 0 | Element* parentElement = aElement->GetParentElement(); |
384 | 0 | MOZ_ASSERT(parentElement); |
385 | 0 | PseudoElementHashEntry::KeyType key = { parentElement, pseudoType }; |
386 | 0 | elementsToRestyle.Remove(key); |
387 | 0 | } |
388 | 0 | } |
389 | | |
390 | | void |
391 | | EffectCompositor::UpdateEffectProperties(const ComputedStyle* aStyle, |
392 | | Element* aElement, |
393 | | CSSPseudoElementType aPseudoType) |
394 | 0 | { |
395 | 0 | EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType); |
396 | 0 | if (!effectSet) { |
397 | 0 | return; |
398 | 0 | } |
399 | 0 | |
400 | 0 | // Style context (Gecko) or computed values (Stylo) change might cause CSS |
401 | 0 | // cascade level, e.g removing !important, so we should update the cascading |
402 | 0 | // result. |
403 | 0 | effectSet->MarkCascadeNeedsUpdate(); |
404 | 0 |
|
405 | 0 | for (KeyframeEffect* effect : *effectSet) { |
406 | 0 | effect->UpdateProperties(aStyle); |
407 | 0 | } |
408 | 0 | } |
409 | | |
410 | | |
411 | | namespace { |
412 | | class EffectCompositeOrderComparator { |
413 | | public: |
414 | | bool Equals(const KeyframeEffect* a, const KeyframeEffect* b) const |
415 | 0 | { |
416 | 0 | return a == b; |
417 | 0 | } |
418 | | |
419 | | bool LessThan(const KeyframeEffect* a, const KeyframeEffect* b) const |
420 | 0 | { |
421 | 0 | MOZ_ASSERT(a->GetAnimation() && b->GetAnimation()); |
422 | 0 | MOZ_ASSERT( |
423 | 0 | Equals(a, b) || |
424 | 0 | a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()) != |
425 | 0 | b->GetAnimation()->HasLowerCompositeOrderThan(*a->GetAnimation())); |
426 | 0 | return a->GetAnimation()->HasLowerCompositeOrderThan(*b->GetAnimation()); |
427 | 0 | } |
428 | | }; |
429 | | } |
430 | | |
431 | | bool |
432 | | EffectCompositor::GetServoAnimationRule( |
433 | | const dom::Element* aElement, |
434 | | CSSPseudoElementType aPseudoType, |
435 | | CascadeLevel aCascadeLevel, |
436 | | RawServoAnimationValueMapBorrowedMut aAnimationValues) |
437 | 0 | { |
438 | 0 | MOZ_ASSERT(aAnimationValues); |
439 | 0 | MOZ_ASSERT(mPresContext && mPresContext->IsDynamic(), |
440 | 0 | "Should not be in print preview"); |
441 | 0 | // Gecko_GetAnimationRule should have already checked this |
442 | 0 | MOZ_ASSERT(nsContentUtils::GetPresShellForContent(aElement), |
443 | 0 | "Should not be trying to run animations on elements in documents" |
444 | 0 | " without a pres shell (e.g. XMLHttpRequest documents)"); |
445 | 0 |
|
446 | 0 | EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType); |
447 | 0 | if (!effectSet) { |
448 | 0 | return false; |
449 | 0 | } |
450 | 0 | |
451 | 0 | // Get a list of effects sorted by composite order. |
452 | 0 | nsTArray<KeyframeEffect*> sortedEffectList(effectSet->Count()); |
453 | 0 | for (KeyframeEffect* effect : *effectSet) { |
454 | 0 | sortedEffectList.AppendElement(effect); |
455 | 0 | } |
456 | 0 | sortedEffectList.Sort(EffectCompositeOrderComparator()); |
457 | 0 |
|
458 | 0 | // If multiple animations affect the same property, animations with higher |
459 | 0 | // composite order (priority) override or add or animations with lower |
460 | 0 | // priority. |
461 | 0 | const nsCSSPropertyIDSet propertiesToSkip = |
462 | 0 | aCascadeLevel == CascadeLevel::Animations |
463 | 0 | ? effectSet->PropertiesForAnimationsLevel().Inverse() |
464 | 0 | : effectSet->PropertiesForAnimationsLevel(); |
465 | 0 | for (KeyframeEffect* effect : sortedEffectList) { |
466 | 0 | effect->GetAnimation()->ComposeStyle(*aAnimationValues, propertiesToSkip); |
467 | 0 | } |
468 | 0 |
|
469 | 0 | MOZ_ASSERT(effectSet == EffectSet::GetEffectSet(aElement, aPseudoType), |
470 | 0 | "EffectSet should not change while composing style"); |
471 | 0 |
|
472 | 0 | return true; |
473 | 0 | } |
474 | | |
475 | | /* static */ dom::Element* |
476 | | EffectCompositor::GetElementToRestyle(dom::Element* aElement, |
477 | | CSSPseudoElementType aPseudoType) |
478 | 0 | { |
479 | 0 | if (aPseudoType == CSSPseudoElementType::NotPseudo) { |
480 | 0 | return aElement; |
481 | 0 | } |
482 | 0 | |
483 | 0 | if (aPseudoType == CSSPseudoElementType::before) { |
484 | 0 | return nsLayoutUtils::GetBeforePseudo(aElement); |
485 | 0 | } |
486 | 0 | |
487 | 0 | if (aPseudoType == CSSPseudoElementType::after) { |
488 | 0 | return nsLayoutUtils::GetAfterPseudo(aElement); |
489 | 0 | } |
490 | 0 | |
491 | 0 | MOZ_ASSERT_UNREACHABLE("Should not try to get the element to restyle for " |
492 | 0 | "a pseudo other that :before or :after"); |
493 | 0 | return nullptr; |
494 | 0 | } |
495 | | |
496 | | bool |
497 | | EffectCompositor::HasPendingStyleUpdates() const |
498 | 0 | { |
499 | 0 | for (auto& elementSet : mElementsToRestyle) { |
500 | 0 | if (elementSet.Count()) { |
501 | 0 | return true; |
502 | 0 | } |
503 | 0 | } |
504 | 0 |
|
505 | 0 | return false; |
506 | 0 | } |
507 | | |
508 | | |
509 | | /* static */ bool |
510 | | EffectCompositor::HasAnimationsForCompositor(const nsIFrame* aFrame, |
511 | | nsCSSPropertyID aProperty) |
512 | 0 | { |
513 | 0 | return FindAnimationsForCompositor(aFrame, aProperty, nullptr); |
514 | 0 | } |
515 | | |
516 | | /* static */ nsTArray<RefPtr<dom::Animation>> |
517 | | EffectCompositor::GetAnimationsForCompositor(const nsIFrame* aFrame, |
518 | | nsCSSPropertyID aProperty) |
519 | 0 | { |
520 | 0 | nsTArray<RefPtr<dom::Animation>> result; |
521 | 0 |
|
522 | | #ifdef DEBUG |
523 | | bool foundSome = |
524 | | #endif |
525 | | FindAnimationsForCompositor(aFrame, aProperty, &result); |
526 | 0 | MOZ_ASSERT(!foundSome || !result.IsEmpty(), |
527 | 0 | "If return value is true, matches array should be non-empty"); |
528 | 0 |
|
529 | 0 | return result; |
530 | 0 | } |
531 | | |
532 | | /* static */ void |
533 | | EffectCompositor::ClearIsRunningOnCompositor(const nsIFrame *aFrame, |
534 | | nsCSSPropertyID aProperty) |
535 | 0 | { |
536 | 0 | EffectSet* effects = EffectSet::GetEffectSet(aFrame); |
537 | 0 | if (!effects) { |
538 | 0 | return; |
539 | 0 | } |
540 | 0 | |
541 | 0 | for (KeyframeEffect* effect : *effects) { |
542 | 0 | effect->SetIsRunningOnCompositor(aProperty, false); |
543 | 0 | } |
544 | 0 | } |
545 | | |
546 | | /* static */ void |
547 | | EffectCompositor::MaybeUpdateCascadeResults(Element* aElement, |
548 | | CSSPseudoElementType aPseudoType) |
549 | 0 | { |
550 | 0 | EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType); |
551 | 0 | if (!effects || !effects->CascadeNeedsUpdate()) { |
552 | 0 | return; |
553 | 0 | } |
554 | 0 | |
555 | 0 | UpdateCascadeResults(*effects, aElement, aPseudoType); |
556 | 0 |
|
557 | 0 | MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state"); |
558 | 0 | } |
559 | | |
560 | | /* static */ Maybe<NonOwningAnimationTarget> |
561 | | EffectCompositor::GetAnimationElementAndPseudoForFrame(const nsIFrame* aFrame) |
562 | 0 | { |
563 | 0 | // Always return the same object to benefit from return-value optimization. |
564 | 0 | Maybe<NonOwningAnimationTarget> result; |
565 | 0 |
|
566 | 0 | CSSPseudoElementType pseudoType = |
567 | 0 | aFrame->Style()->GetPseudoType(); |
568 | 0 |
|
569 | 0 | if (pseudoType != CSSPseudoElementType::NotPseudo && |
570 | 0 | pseudoType != CSSPseudoElementType::before && |
571 | 0 | pseudoType != CSSPseudoElementType::after) { |
572 | 0 | return result; |
573 | 0 | } |
574 | 0 | |
575 | 0 | nsIContent* content = aFrame->GetContent(); |
576 | 0 | if (!content) { |
577 | 0 | return result; |
578 | 0 | } |
579 | 0 | |
580 | 0 | if (pseudoType == CSSPseudoElementType::before || |
581 | 0 | pseudoType == CSSPseudoElementType::after) { |
582 | 0 | content = content->GetParent(); |
583 | 0 | if (!content) { |
584 | 0 | return result; |
585 | 0 | } |
586 | 0 | } |
587 | 0 | |
588 | 0 | if (!content->IsElement()) { |
589 | 0 | return result; |
590 | 0 | } |
591 | 0 | |
592 | 0 | result.emplace(content->AsElement(), pseudoType); |
593 | 0 |
|
594 | 0 | return result; |
595 | 0 | } |
596 | | |
597 | | |
598 | | /* static */ nsCSSPropertyIDSet |
599 | | EffectCompositor::GetOverriddenProperties(EffectSet& aEffectSet, |
600 | | Element* aElement, |
601 | | CSSPseudoElementType aPseudoType) |
602 | 0 | { |
603 | 0 | MOZ_ASSERT(aElement, "Should have an element to get style data from"); |
604 | 0 |
|
605 | 0 | nsCSSPropertyIDSet result; |
606 | 0 |
|
607 | 0 | Element* elementToRestyle = GetElementToRestyle(aElement, aPseudoType); |
608 | 0 | if (!elementToRestyle) { |
609 | 0 | return result; |
610 | 0 | } |
611 | 0 | |
612 | 0 | AutoTArray<nsCSSPropertyID, LayerAnimationInfo::kRecords> propertiesToTrack; |
613 | 0 | { |
614 | 0 | nsCSSPropertyIDSet propertiesToTrackAsSet; |
615 | 0 | for (KeyframeEffect* effect : aEffectSet) { |
616 | 0 | for (const AnimationProperty& property : effect->Properties()) { |
617 | 0 | if (nsCSSProps::PropHasFlags(property.mProperty, |
618 | 0 | CSSPropFlags::CanAnimateOnCompositor) && |
619 | 0 | !propertiesToTrackAsSet.HasProperty(property.mProperty)) { |
620 | 0 | propertiesToTrackAsSet.AddProperty(property.mProperty); |
621 | 0 | propertiesToTrack.AppendElement(property.mProperty); |
622 | 0 | } |
623 | 0 | } |
624 | 0 | // Skip iterating over the rest of the effects if we've already |
625 | 0 | // found all the compositor-animatable properties. |
626 | 0 | if (propertiesToTrack.Length() == LayerAnimationInfo::kRecords) { |
627 | 0 | break; |
628 | 0 | } |
629 | 0 | } |
630 | 0 | } |
631 | 0 |
|
632 | 0 | if (propertiesToTrack.IsEmpty()) { |
633 | 0 | return result; |
634 | 0 | } |
635 | 0 | |
636 | 0 | Servo_GetProperties_Overriding_Animation(elementToRestyle, |
637 | 0 | &propertiesToTrack, |
638 | 0 | &result); |
639 | 0 | return result; |
640 | 0 | } |
641 | | |
642 | | /* static */ void |
643 | | EffectCompositor::UpdateCascadeResults(EffectSet& aEffectSet, |
644 | | Element* aElement, |
645 | | CSSPseudoElementType aPseudoType) |
646 | 0 | { |
647 | 0 | MOZ_ASSERT(EffectSet::GetEffectSet(aElement, aPseudoType) == &aEffectSet, |
648 | 0 | "Effect set should correspond to the specified (pseudo-)element"); |
649 | 0 | if (aEffectSet.IsEmpty()) { |
650 | 0 | aEffectSet.MarkCascadeUpdated(); |
651 | 0 | return; |
652 | 0 | } |
653 | 0 | |
654 | 0 | // Get a list of effects sorted by composite order. |
655 | 0 | nsTArray<KeyframeEffect*> sortedEffectList(aEffectSet.Count()); |
656 | 0 | for (KeyframeEffect* effect : aEffectSet) { |
657 | 0 | sortedEffectList.AppendElement(effect); |
658 | 0 | } |
659 | 0 | sortedEffectList.Sort(EffectCompositeOrderComparator()); |
660 | 0 |
|
661 | 0 | // Get properties that override the *animations* level of the cascade. |
662 | 0 | // |
663 | 0 | // We only do this for properties that we can animate on the compositor |
664 | 0 | // since we will apply other properties on the main thread where the usual |
665 | 0 | // cascade applies. |
666 | 0 | nsCSSPropertyIDSet overriddenProperties = |
667 | 0 | GetOverriddenProperties(aEffectSet, aElement, aPseudoType); |
668 | 0 |
|
669 | 0 | // Returns a bitset the represents which properties from |
670 | 0 | // LayerAnimationInfo::sRecords are present in |aPropertySet|. |
671 | 0 | auto compositorPropertiesInSet = |
672 | 0 | [](nsCSSPropertyIDSet& aPropertySet) -> |
673 | 0 | std::bitset<LayerAnimationInfo::kRecords> { |
674 | 0 | std::bitset<LayerAnimationInfo::kRecords> result; |
675 | 0 | for (size_t i = 0; i < LayerAnimationInfo::kRecords; i++) { |
676 | 0 | if (aPropertySet.HasProperty( |
677 | 0 | LayerAnimationInfo::sRecords[i].mProperty)) { |
678 | 0 | result.set(i); |
679 | 0 | } |
680 | 0 | } |
681 | 0 | return result; |
682 | 0 | }; |
683 | 0 |
|
684 | 0 | nsCSSPropertyIDSet& propertiesWithImportantRules = |
685 | 0 | aEffectSet.PropertiesWithImportantRules(); |
686 | 0 | nsCSSPropertyIDSet& propertiesForAnimationsLevel = |
687 | 0 | aEffectSet.PropertiesForAnimationsLevel(); |
688 | 0 |
|
689 | 0 | // Record which compositor-animatable properties were originally set so we can |
690 | 0 | // compare for changes later. |
691 | 0 | std::bitset<LayerAnimationInfo::kRecords> |
692 | 0 | prevCompositorPropertiesWithImportantRules = |
693 | 0 | compositorPropertiesInSet(propertiesWithImportantRules); |
694 | 0 |
|
695 | 0 | nsCSSPropertyIDSet prevPropertiesForAnimationsLevel = |
696 | 0 | propertiesForAnimationsLevel; |
697 | 0 |
|
698 | 0 | propertiesWithImportantRules.Empty(); |
699 | 0 | propertiesForAnimationsLevel.Empty(); |
700 | 0 |
|
701 | 0 | nsCSSPropertyIDSet propertiesForTransitionsLevel; |
702 | 0 |
|
703 | 0 | for (const KeyframeEffect* effect : sortedEffectList) { |
704 | 0 | MOZ_ASSERT(effect->GetAnimation(), |
705 | 0 | "Effects on a target element should have an Animation"); |
706 | 0 | CascadeLevel cascadeLevel = effect->GetAnimation()->CascadeLevel(); |
707 | 0 |
|
708 | 0 | for (const AnimationProperty& prop : effect->Properties()) { |
709 | 0 | if (overriddenProperties.HasProperty(prop.mProperty)) { |
710 | 0 | propertiesWithImportantRules.AddProperty(prop.mProperty); |
711 | 0 | } |
712 | 0 |
|
713 | 0 | switch (cascadeLevel) { |
714 | 0 | case EffectCompositor::CascadeLevel::Animations: |
715 | 0 | propertiesForAnimationsLevel.AddProperty(prop.mProperty); |
716 | 0 | break; |
717 | 0 | case EffectCompositor::CascadeLevel::Transitions: |
718 | 0 | propertiesForTransitionsLevel.AddProperty(prop.mProperty); |
719 | 0 | break; |
720 | 0 | } |
721 | 0 | } |
722 | 0 | } |
723 | 0 |
|
724 | 0 | aEffectSet.MarkCascadeUpdated(); |
725 | 0 |
|
726 | 0 | nsPresContext* presContext = nsContentUtils::GetContextForContent(aElement); |
727 | 0 | if (!presContext) { |
728 | 0 | return; |
729 | 0 | } |
730 | 0 | |
731 | 0 | // If properties for compositor are newly overridden by !important rules, or |
732 | 0 | // released from being overridden by !important rules, we need to update |
733 | 0 | // layers for animations level because it's a trigger to send animations to |
734 | 0 | // the compositor or pull animations back from the compositor. |
735 | 0 | if (prevCompositorPropertiesWithImportantRules != |
736 | 0 | compositorPropertiesInSet(propertiesWithImportantRules)) { |
737 | 0 | presContext->EffectCompositor()-> |
738 | 0 | RequestRestyle(aElement, aPseudoType, |
739 | 0 | EffectCompositor::RestyleType::Layer, |
740 | 0 | EffectCompositor::CascadeLevel::Animations); |
741 | 0 | } |
742 | 0 |
|
743 | 0 | // If we have transition properties and if the same propery for animations |
744 | 0 | // level is newly added or removed, we need to update the transition level |
745 | 0 | // rule since the it will be added/removed from the rule tree. |
746 | 0 | nsCSSPropertyIDSet changedPropertiesForAnimationLevel = |
747 | 0 | prevPropertiesForAnimationsLevel.Xor(propertiesForAnimationsLevel); |
748 | 0 | nsCSSPropertyIDSet commonProperties = |
749 | 0 | propertiesForTransitionsLevel.Intersect( |
750 | 0 | changedPropertiesForAnimationLevel); |
751 | 0 | if (!commonProperties.IsEmpty()) { |
752 | 0 | EffectCompositor::RestyleType restyleType = |
753 | 0 | compositorPropertiesInSet(changedPropertiesForAnimationLevel).none() |
754 | 0 | ? EffectCompositor::RestyleType::Standard |
755 | 0 | : EffectCompositor::RestyleType::Layer; |
756 | 0 | presContext->EffectCompositor()-> |
757 | 0 | RequestRestyle(aElement, aPseudoType, |
758 | 0 | restyleType, |
759 | 0 | EffectCompositor::CascadeLevel::Transitions); |
760 | 0 | } |
761 | 0 | } |
762 | | |
763 | | /* static */ void |
764 | | EffectCompositor::SetPerformanceWarning( |
765 | | const nsIFrame *aFrame, |
766 | | nsCSSPropertyID aProperty, |
767 | | const AnimationPerformanceWarning& aWarning) |
768 | 0 | { |
769 | 0 | EffectSet* effects = EffectSet::GetEffectSet(aFrame); |
770 | 0 | if (!effects) { |
771 | 0 | return; |
772 | 0 | } |
773 | 0 | |
774 | 0 | for (KeyframeEffect* effect : *effects) { |
775 | 0 | effect->SetPerformanceWarning(aProperty, aWarning); |
776 | 0 | } |
777 | 0 | } |
778 | | |
779 | | bool |
780 | | EffectCompositor::PreTraverse(ServoTraversalFlags aFlags) |
781 | 0 | { |
782 | 0 | return PreTraverseInSubtree(aFlags, nullptr); |
783 | 0 | } |
784 | | |
785 | | bool |
786 | | EffectCompositor::PreTraverseInSubtree(ServoTraversalFlags aFlags, |
787 | | Element* aRoot) |
788 | 0 | { |
789 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
790 | 0 | MOZ_ASSERT(!aRoot || nsContentUtils::GetPresShellForContent(aRoot), |
791 | 0 | "Traversal root, if provided, should be bound to a display " |
792 | 0 | "document"); |
793 | 0 |
|
794 | 0 | // Convert the root element to the parent element if the root element is |
795 | 0 | // pseudo since we check each element in mElementsToRestyle is in the subtree |
796 | 0 | // of the root element later in this function, but for pseudo elements the |
797 | 0 | // element in mElementsToRestyle is the parent of the pseudo. |
798 | 0 | if (aRoot && |
799 | 0 | (aRoot->IsGeneratedContentContainerForBefore() || |
800 | 0 | aRoot->IsGeneratedContentContainerForAfter())) { |
801 | 0 | aRoot = aRoot->GetParentElement(); |
802 | 0 | } |
803 | 0 |
|
804 | 0 | AutoRestore<bool> guard(mIsInPreTraverse); |
805 | 0 | mIsInPreTraverse = true; |
806 | 0 |
|
807 | 0 | // We need to force flush all throttled animations if we also have |
808 | 0 | // non-animation restyles (since we'll want the up-to-date animation style |
809 | 0 | // when we go to process them so we can trigger transitions correctly), and |
810 | 0 | // if we are currently flushing all throttled animation restyles. |
811 | 0 | bool flushThrottledRestyles = |
812 | 0 | (aRoot && aRoot->HasDirtyDescendantsForServo()) || |
813 | 0 | (aFlags & ServoTraversalFlags::FlushThrottledAnimations); |
814 | 0 |
|
815 | 0 | using ElementsToRestyleIterType = |
816 | 0 | nsDataHashtable<PseudoElementHashEntry, bool>::Iterator; |
817 | 0 | auto getNeededRestyleTarget = [&](const ElementsToRestyleIterType& aIter) |
818 | 0 | -> NonOwningAnimationTarget { |
819 | 0 | NonOwningAnimationTarget returnTarget; |
820 | 0 |
|
821 | 0 | // If aIter.Data() is false, the element only requested a throttled |
822 | 0 | // (skippable) restyle, so we can skip it if flushThrottledRestyles is not |
823 | 0 | // true. |
824 | 0 | if (!flushThrottledRestyles && !aIter.Data()) { |
825 | 0 | return returnTarget; |
826 | 0 | } |
827 | 0 | |
828 | 0 | const NonOwningAnimationTarget& target = aIter.Key(); |
829 | 0 |
|
830 | 0 | // Skip elements in documents without a pres shell. Normally we filter out |
831 | 0 | // such elements in RequestRestyle but it can happen that, after adding |
832 | 0 | // them to mElementsToRestyle, they are transferred to a different document. |
833 | 0 | // |
834 | 0 | // We will drop them from mElementsToRestyle at the end of the next full |
835 | 0 | // document restyle (at the end of this function) but for consistency with |
836 | 0 | // how we treat such elements in RequestRestyle, we just ignore them here. |
837 | 0 | if (!nsContentUtils::GetPresShellForContent(target.mElement)) { |
838 | 0 | return returnTarget; |
839 | 0 | } |
840 | 0 | |
841 | 0 | // Ignore restyles that aren't in the flattened tree subtree rooted at |
842 | 0 | // aRoot. |
843 | 0 | if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOfForStyle( |
844 | 0 | target.mElement, aRoot)) { |
845 | 0 | return returnTarget; |
846 | 0 | } |
847 | 0 | |
848 | 0 | returnTarget = target; |
849 | 0 | return returnTarget; |
850 | 0 | }; |
851 | 0 |
|
852 | 0 | bool foundElementsNeedingRestyle = false; |
853 | 0 |
|
854 | 0 | nsTArray<NonOwningAnimationTarget> elementsWithCascadeUpdates; |
855 | 0 | for (size_t i = 0; i < kCascadeLevelCount; ++i) { |
856 | 0 | CascadeLevel cascadeLevel = CascadeLevel(i); |
857 | 0 | auto& elementSet = mElementsToRestyle[cascadeLevel]; |
858 | 0 | for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { |
859 | 0 | const NonOwningAnimationTarget& target = getNeededRestyleTarget(iter); |
860 | 0 | if (!target.mElement) { |
861 | 0 | continue; |
862 | 0 | } |
863 | 0 | |
864 | 0 | EffectSet* effects = EffectSet::GetEffectSet(target.mElement, |
865 | 0 | target.mPseudoType); |
866 | 0 | if (!effects || !effects->CascadeNeedsUpdate()) { |
867 | 0 | continue; |
868 | 0 | } |
869 | 0 | |
870 | 0 | elementsWithCascadeUpdates.AppendElement(target); |
871 | 0 | } |
872 | 0 | } |
873 | 0 |
|
874 | 0 | for (const NonOwningAnimationTarget& target: elementsWithCascadeUpdates) { |
875 | 0 | MaybeUpdateCascadeResults(target.mElement, target.mPseudoType); |
876 | 0 | } |
877 | 0 | elementsWithCascadeUpdates.Clear(); |
878 | 0 |
|
879 | 0 | for (size_t i = 0; i < kCascadeLevelCount; ++i) { |
880 | 0 | CascadeLevel cascadeLevel = CascadeLevel(i); |
881 | 0 | auto& elementSet = mElementsToRestyle[cascadeLevel]; |
882 | 0 | for (auto iter = elementSet.Iter(); !iter.Done(); iter.Next()) { |
883 | 0 | const NonOwningAnimationTarget& target = getNeededRestyleTarget(iter); |
884 | 0 | if (!target.mElement) { |
885 | 0 | continue; |
886 | 0 | } |
887 | 0 | |
888 | 0 | // We need to post restyle hints even if the target is not in EffectSet to |
889 | 0 | // ensure the final restyling for removed animations. |
890 | 0 | // We can't call PostRestyleEvent directly here since we are still in the |
891 | 0 | // middle of the servo traversal. |
892 | 0 | mPresContext->RestyleManager()-> |
893 | 0 | PostRestyleEventForAnimations(target.mElement, |
894 | 0 | target.mPseudoType, |
895 | 0 | cascadeLevel == CascadeLevel::Transitions |
896 | 0 | ? eRestyle_CSSTransitions |
897 | 0 | : eRestyle_CSSAnimations); |
898 | 0 |
|
899 | 0 | foundElementsNeedingRestyle = true; |
900 | 0 |
|
901 | 0 | EffectSet* effects = EffectSet::GetEffectSet(target.mElement, |
902 | 0 | target.mPseudoType); |
903 | 0 | if (!effects) { |
904 | 0 | // Drop EffectSets that have been destroyed. |
905 | 0 | iter.Remove(); |
906 | 0 | continue; |
907 | 0 | } |
908 | 0 | |
909 | 0 | for (KeyframeEffect* effect : *effects) { |
910 | 0 | effect->GetAnimation()->WillComposeStyle(); |
911 | 0 | } |
912 | 0 |
|
913 | 0 | // Remove the element from the list of elements to restyle since we are |
914 | 0 | // about to restyle it. |
915 | 0 | iter.Remove(); |
916 | 0 | } |
917 | 0 |
|
918 | 0 | // If this is a full document restyle, then unconditionally clear |
919 | 0 | // elementSet in case there are any elements that didn't match above |
920 | 0 | // because they were moved to a document without a pres shell after |
921 | 0 | // posting an animation restyle. |
922 | 0 | if (!aRoot && flushThrottledRestyles) { |
923 | 0 | elementSet.Clear(); |
924 | 0 | } |
925 | 0 | } |
926 | 0 |
|
927 | 0 | return foundElementsNeedingRestyle; |
928 | 0 | } |
929 | | |
930 | | bool |
931 | | EffectCompositor::PreTraverse(dom::Element* aElement, |
932 | | CSSPseudoElementType aPseudoType) |
933 | 0 | { |
934 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
935 | 0 |
|
936 | 0 | // If |aElement|'s document does not have a pres shell, e.g. it is document |
937 | 0 | // without a browsing context such as we might get from an XMLHttpRequest, we |
938 | 0 | // should not run animations on it. |
939 | 0 | if (!nsContentUtils::GetPresShellForContent(aElement)) { |
940 | 0 | return false; |
941 | 0 | } |
942 | 0 | |
943 | 0 | bool found = false; |
944 | 0 | if (aPseudoType != CSSPseudoElementType::NotPseudo && |
945 | 0 | aPseudoType != CSSPseudoElementType::before && |
946 | 0 | aPseudoType != CSSPseudoElementType::after) { |
947 | 0 | return found; |
948 | 0 | } |
949 | 0 | |
950 | 0 | AutoRestore<bool> guard(mIsInPreTraverse); |
951 | 0 | mIsInPreTraverse = true; |
952 | 0 |
|
953 | 0 | PseudoElementHashEntry::KeyType key = { aElement, aPseudoType }; |
954 | 0 |
|
955 | 0 | // We need to flush all throttled animation restyles too if we also have |
956 | 0 | // non-animation restyles (since we'll want the up-to-date animation style |
957 | 0 | // when we go to process them so we can trigger transitions correctly). |
958 | 0 | Element* elementToRestyle = GetElementToRestyle(aElement, aPseudoType); |
959 | 0 | bool flushThrottledRestyles = elementToRestyle && |
960 | 0 | elementToRestyle->HasDirtyDescendantsForServo(); |
961 | 0 |
|
962 | 0 | for (size_t i = 0; i < kCascadeLevelCount; ++i) { |
963 | 0 | CascadeLevel cascadeLevel = CascadeLevel(i); |
964 | 0 | auto& elementSet = mElementsToRestyle[cascadeLevel]; |
965 | 0 |
|
966 | 0 | // Skip if we don't have a restyle, or if we only have a throttled |
967 | 0 | // (skippable) restyle and we're not required to flush throttled restyles. |
968 | 0 | bool hasUnthrottledRestyle = false; |
969 | 0 | if (!elementSet.Get(key, &hasUnthrottledRestyle) || |
970 | 0 | (!flushThrottledRestyles && !hasUnthrottledRestyle)) { |
971 | 0 | continue; |
972 | 0 | } |
973 | 0 | |
974 | 0 | mPresContext->RestyleManager()-> |
975 | 0 | PostRestyleEventForAnimations(aElement, |
976 | 0 | aPseudoType, |
977 | 0 | cascadeLevel == CascadeLevel::Transitions |
978 | 0 | ? eRestyle_CSSTransitions |
979 | 0 | : eRestyle_CSSAnimations); |
980 | 0 |
|
981 | 0 | EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType); |
982 | 0 | if (effects) { |
983 | 0 | MaybeUpdateCascadeResults(aElement, aPseudoType); |
984 | 0 |
|
985 | 0 | for (KeyframeEffect* effect : *effects) { |
986 | 0 | effect->GetAnimation()->WillComposeStyle(); |
987 | 0 | } |
988 | 0 | } |
989 | 0 |
|
990 | 0 | elementSet.Remove(key); |
991 | 0 | found = true; |
992 | 0 | } |
993 | 0 | return found; |
994 | 0 | } |
995 | | |
996 | | |
997 | | } // namespace mozilla |