void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate& update, const Element* animatingElement, double timelineCurrentTime) { ElementAnimations* elementAnimations = animatingElement ? animatingElement->elementAnimations() : nullptr; AnimationStack* animationStack = elementAnimations ? &elementAnimations->defaultStack() : nullptr; ActiveInterpolationsMap activeInterpolationsForTransitions; if (update.newTransitions().isEmpty() && update.cancelledTransitions().isEmpty()) { activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, 0, 0, KeyframeEffect::TransitionPriority, timelineCurrentTime); } else { HeapVector<Member<InertEffect>> newTransitions; for (const auto& entry : update.newTransitions()) newTransitions.append(entry.value.effect.get()); HeapHashSet<Member<const Animation>> cancelledAnimations; if (!update.cancelledTransitions().isEmpty()) { ASSERT(elementAnimations); const TransitionMap& transitionMap = elementAnimations->cssAnimations().m_transitions; for (CSSPropertyID id : update.cancelledTransitions()) { ASSERT(transitionMap.contains(id)); cancelledAnimations.add(transitionMap.get(id).animation.get()); } } activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, &newTransitions, &cancelledAnimations, KeyframeEffect::TransitionPriority, timelineCurrentTime); } // Properties being animated by animations don't get values from transitions applied. if (!update.activeInterpolationsForAnimations().isEmpty() && !activeInterpolationsForTransitions.isEmpty()) { for (const auto& entry : update.activeInterpolationsForAnimations()) activeInterpolationsForTransitions.remove(entry.key); } update.adoptActiveInterpolationsForTransitions(activeInterpolationsForTransitions); }
void nsAnimationManager::FlushAnimations(FlushFlags aFlags) { // FIXME: check that there's at least one style rule that's not // in its "done" state, and if there isn't, remove ourselves from // the refresh driver (but leave the animations!). TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh(); bool didThrottle = false; for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData; l = PR_NEXT_LINK(l)) { ElementAnimations *ea = static_cast<ElementAnimations*>(l); bool canThrottleTick = aFlags == Can_Throttle && ea->CanPerformOnCompositorThread( CommonElementAnimationData::CanAnimateFlags(0)) && ea->CanThrottleAnimation(now); nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule; ea->EnsureStyleRuleFor(now, mPendingEvents, canThrottleTick); if (oldStyleRule != ea->mStyleRule) { ea->PostRestyleForAnimation(mPresContext); } else { didThrottle = true; } } if (didThrottle) { mPresContext->Document()->SetNeedStyleFlush(); } DispatchEvents(); // may destroy us }
/* virtual */ void nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime) { NS_ABORT_IF_FALSE(mPresContext, "refresh driver should not notify additional observers " "after pres context has been destroyed"); if (!mPresContext->GetPresShell()) { // Someone might be keeping mPresContext alive past the point // where it has been torn down; don't bother doing anything in // this case. But do get rid of all our transitions so we stop // triggering refreshes. RemoveAllElementData(); return; } // FIXME: check that there's at least one style rule that's not // in its "done" state, and if there isn't, remove ourselves from // the refresh driver (but leave the animations!). for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData; l = PR_NEXT_LINK(l)) { ElementAnimations *ea = static_cast<ElementAnimations*>(l); nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule; bool shouldInterpolate = !ea->CanPerformOnCompositorThread(); ea->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(), mPendingEvents, shouldInterpolate); if (oldStyleRule != ea->mStyleRule && shouldInterpolate) { ea->PostRestyleForAnimation(mPresContext); } } DispatchEvents(); // may destroy us }
nsIStyleRule* nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement, nsCSSPseudoElements::Type aPseudoType) { NS_ABORT_IF_FALSE( aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement || aPseudoType == nsCSSPseudoElements::ePseudo_before || aPseudoType == nsCSSPseudoElements::ePseudo_after, "forbidden pseudo type"); ElementAnimations *ea = GetElementAnimations(aElement, aPseudoType, false); if (!ea) { return nsnull; } NS_WARN_IF_FALSE(ea->mStyleRuleRefreshTime == mPresContext->RefreshDriver()->MostRecentRefresh(), "should already have refreshed style rule"); if (mPresContext->IsProcessingRestyles() && !mPresContext->IsProcessingAnimationStyleChange()) { // During the non-animation part of processing restyles, we don't // add the animation rule. if (ea->mStyleRule) { ea->PostRestyleForAnimation(mPresContext); } return nsnull; } return ea->mStyleRule; }
void CompositorAnimations::cancelIncompatibleAnimationsOnCompositor(const Element& targetElement, const Animation& animationToAdd, const EffectModel& effectToAdd) { const bool affectsOpacity = effectToAdd.affects(PropertyHandle(CSSPropertyOpacity)); const bool affectsTransform = effectToAdd.isTransformRelatedEffect(); const bool affectsFilter = effectToAdd.affects(PropertyHandle(CSSPropertyWebkitFilter)); const bool affectsBackdropFilter = effectToAdd.affects(PropertyHandle(CSSPropertyBackdropFilter)); if (!targetElement.hasAnimations()) return; ElementAnimations* elementAnimations = targetElement.elementAnimations(); ASSERT(elementAnimations); for (const auto& entry : elementAnimations->animations()) { Animation* attachedAnimation = entry.key; if (!considerAnimationAsIncompatible(*attachedAnimation, animationToAdd)) continue; if ((affectsOpacity && attachedAnimation->affects(targetElement, CSSPropertyOpacity)) || (affectsTransform && isTransformRelatedAnimation(targetElement, attachedAnimation)) || (affectsFilter && attachedAnimation->affects(targetElement, CSSPropertyWebkitFilter)) || (affectsBackdropFilter && attachedAnimation->affects(targetElement, CSSPropertyBackdropFilter))) attachedAnimation->cancelAnimationOnCompositor(); } }
void CSSAnimations::calculateCompositorAnimationUpdate(CSSAnimationUpdate& update, const Element* animatingElement, Element& element, const ComputedStyle& style) { ElementAnimations* elementAnimations = animatingElement ? animatingElement->elementAnimations() : nullptr; // We only update compositor animations in response to changes in the base style. if (!elementAnimations || elementAnimations->isAnimationStyleChange()) return; if (!animatingElement->layoutObject() || !animatingElement->layoutObject()->style()) return; const ComputedStyle& oldStyle = *animatingElement->layoutObject()->style(); if (!oldStyle.shouldCompositeForCurrentAnimations()) return; CSSAnimations& cssAnimations = elementAnimations->cssAnimations(); for (auto& runningAnimation : cssAnimations.m_animations.values()) { Animation& animation = *runningAnimation->animation; if (animation.effect() && animation.effect()->isKeyframeEffect()) { EffectModel* model = toKeyframeEffect(animation.effect())->model(); if (model && model->isKeyframeEffectModel()) { KeyframeEffectModelBase* keyframeEffect = toKeyframeEffectModelBase(model); if (keyframeEffect->hasSyntheticKeyframes() && keyframeEffect->snapshotNeutralCompositorKeyframes(element, oldStyle, style)) update.updateCompositorKeyframes(&animation); } } } if (oldStyle.hasCurrentTransformAnimation() && oldStyle.effectiveZoom() != style.effectiveZoom()) { for (auto& entry : elementAnimations->animations()) { Animation& animation = *entry.key; if (animation.effect() && animation.effect()->isKeyframeEffect()) { EffectModel* model = toKeyframeEffect(animation.effect())->model(); if (model && model->isKeyframeEffectModel()) { KeyframeEffectModelBase* keyframeEffect = toKeyframeEffectModelBase(model); if (keyframeEffect->affects(PropertyHandle(CSSPropertyTransform)) && keyframeEffect->snapshotAllCompositorKeyframes(element, &style)) update.updateCompositorKeyframes(&animation); } } } } }
void CSSAnimations::calculateAnimationActiveInterpolations(CSSAnimationUpdate& update, const Element* animatingElement, double timelineCurrentTime) { ElementAnimations* elementAnimations = animatingElement ? animatingElement->elementAnimations() : nullptr; AnimationStack* animationStack = elementAnimations ? &elementAnimations->defaultStack() : nullptr; if (update.newAnimations().isEmpty() && update.suppressedAnimations().isEmpty()) { ActiveInterpolationsMap activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, 0, 0, KeyframeEffect::DefaultPriority, timelineCurrentTime)); update.adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations); return; } HeapVector<Member<InertEffect>> newEffects; for (const auto& newAnimation : update.newAnimations()) newEffects.append(newAnimation.effect.get()); for (const auto& updatedAnimation : update.animationsWithUpdates()) newEffects.append(updatedAnimation.effect.get()); // Animations with updates use a temporary InertEffect for the current frame. ActiveInterpolationsMap activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, &newEffects, &update.suppressedAnimations(), KeyframeEffect::DefaultPriority, timelineCurrentTime)); update.adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations); }
void CompositorAnimations::cancelIncompatibleAnimationsOnCompositor(const Element& targetElement, const AnimationPlayer& playerToAdd, const AnimationEffect& effectToAdd) { const bool affectsOpacity = effectToAdd.affects(CSSPropertyOpacity); const bool affectsTransform = effectToAdd.affects(CSSPropertyTransform); const bool affectsFilter = effectToAdd.affects(CSSPropertyWebkitFilter); if (!targetElement.hasAnimations()) return; ElementAnimations* elementAnimations = targetElement.elementAnimations(); ASSERT(elementAnimations); for (const auto& entry : elementAnimations->players()) { AnimationPlayer* attachedPlayer = entry.key; if (!considerPlayerAsIncompatible(*attachedPlayer, playerToAdd)) continue; if ((affectsOpacity && attachedPlayer->affects(targetElement, CSSPropertyOpacity)) || (affectsTransform && attachedPlayer->affects(targetElement, CSSPropertyTransform)) || (affectsFilter && attachedPlayer->affects(targetElement, CSSPropertyWebkitFilter))) attachedPlayer->cancelAnimationOnCompositor(); } }
void CSSAnimations::calculateTransitionUpdate(CSSAnimationUpdate& update, const Element* animatingElement, const ComputedStyle& style) { if (!animatingElement) return; if (animatingElement->document().printing() || animatingElement->document().wasPrinting()) return; ElementAnimations* elementAnimations = animatingElement->elementAnimations(); const TransitionMap* activeTransitions = elementAnimations ? &elementAnimations->cssAnimations().m_transitions : nullptr; const CSSTransitionData* transitionData = style.transitions(); #if ENABLE(ASSERT) // In debug builds we verify that it would have been safe to avoid populating and testing listedProperties if the style recalc is due to animation. const bool animationStyleRecalc = false; #else // In release builds we avoid the cost of checking for new and interrupted transitions if the style recalc is due to animation. const bool animationStyleRecalc = elementAnimations && elementAnimations->isAnimationStyleChange(); #endif BitArray<numCSSProperties> listedProperties; bool anyTransitionHadTransitionAll = false; const LayoutObject* layoutObject = animatingElement->layoutObject(); if (!animationStyleRecalc && style.display() != NONE && layoutObject && layoutObject->style() && transitionData) { const ComputedStyle& oldStyle = *layoutObject->style(); for (size_t i = 0; i < transitionData->propertyList().size(); ++i) { const CSSTransitionData::TransitionProperty& transitionProperty = transitionData->propertyList()[i]; if (transitionProperty.propertyType != CSSTransitionData::TransitionKnownProperty) continue; CSSPropertyID property = resolveCSSPropertyID(transitionProperty.unresolvedProperty); bool animateAll = property == CSSPropertyAll; if (animateAll) anyTransitionHadTransitionAll = true; const StylePropertyShorthand& propertyList = animateAll ? CSSAnimations::propertiesForTransitionAll() : shorthandForProperty(property); // If not a shorthand we only execute one iteration of this loop, and refer to the property directly. for (unsigned j = 0; !j || j < propertyList.length(); ++j) { CSSPropertyID id = propertyList.length() ? propertyList.properties()[j] : property; ASSERT(id >= firstCSSProperty); if (!animateAll) { if (CSSPropertyMetadata::isInterpolableProperty(id)) listedProperties.set(id - firstCSSProperty); else continue; } // FIXME: We should transition if an !important property changes even when an animation is running, // but this is a bit hard to do with the current applyMatchedProperties system. PropertyHandle property = PropertyHandle(id); if (!update.activeInterpolationsForAnimations().contains(property) && (!elementAnimations || !elementAnimations->cssAnimations().m_previousActiveInterpolationsForAnimations.contains(property))) { calculateTransitionUpdateForProperty(id, *transitionData, i, oldStyle, style, activeTransitions, update, animatingElement); } } } } if (activeTransitions) { for (const auto& entry : *activeTransitions) { CSSPropertyID id = entry.key; if (!anyTransitionHadTransitionAll && !animationStyleRecalc && !listedProperties.get(id - firstCSSProperty)) { // TODO: Figure out why this fails on Chrome OS login page. crbug.com/365507 // ASSERT(animation.playStateInternal() == Animation::Finished || !(elementAnimations && elementAnimations->isAnimationStyleChange())); update.cancelTransition(id); } else if (entry.value.animation->finishedInternal()) { update.finishTransition(id); } } } }
nsIStyleRule* nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, mozilla::dom::Element* aElement) { if (!mPresContext->IsProcessingAnimationStyleChange()) { if (!mPresContext->IsDynamic()) { // For print or print preview, ignore animations. return nullptr; } // Everything that causes our animation data to change triggers a // style change, which in turn triggers a non-animation restyle. // Likewise, when we initially construct frames, we're not in a // style change, but also not in an animation restyle. const nsStyleDisplay *disp = aStyleContext->StyleDisplay(); ElementAnimations *ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(), false); if (!ea && disp->mAnimationNameCount == 1 && disp->mAnimations[0].GetName().IsEmpty()) { return nullptr; } // build the animations list InfallibleTArray<ElementAnimation> newAnimations; BuildAnimations(aStyleContext, newAnimations); if (newAnimations.IsEmpty()) { if (ea) { ea->Destroy(); } return nullptr; } TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); if (ea) { ea->mStyleRule = nullptr; ea->mStyleRuleRefreshTime = TimeStamp(); ea->UpdateAnimationGeneration(mPresContext); // Copy over the start times and (if still paused) pause starts // for each animation (matching on name only) that was also in the // old list of animations. // This means that we honor dynamic changes, which isn't what the // spec says to do, but WebKit seems to honor at least some of // them. See // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html // In order to honor what the spec said, we'd copy more data over // (or potentially optimize BuildAnimations to avoid rebuilding it // in the first place). if (!ea->mAnimations.IsEmpty()) { for (uint32_t newIdx = 0, newEnd = newAnimations.Length(); newIdx != newEnd; ++newIdx) { ElementAnimation *newAnim = &newAnimations[newIdx]; // Find the matching animation with this name in the old list // of animations. Because of this code, they must all have // the same start time, though they might differ in pause // state. So if a page uses multiple copies of the same // animation in one element's animation list, and gives them // different pause states, they, well, get what they deserve. // We'll use the last one since it's more likely to be the one // doing something. const ElementAnimation *oldAnim = nullptr; for (uint32_t oldIdx = ea->mAnimations.Length(); oldIdx-- != 0; ) { const ElementAnimation *a = &ea->mAnimations[oldIdx]; if (a->mName == newAnim->mName) { oldAnim = a; break; } } if (!oldAnim) { continue; } newAnim->mStartTime = oldAnim->mStartTime; newAnim->mLastNotification = oldAnim->mLastNotification; if (oldAnim->IsPaused()) { if (newAnim->IsPaused()) { // Copy pause start just like start time. newAnim->mPauseStart = oldAnim->mPauseStart; } else { // Handle change in pause state by adjusting start // time to unpause. newAnim->mStartTime += refreshTime - oldAnim->mPauseStart; } } } } } else { ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(), true); } ea->mAnimations.SwapElements(newAnimations); ea->mNeedsRefreshes = true; ea->EnsureStyleRuleFor(refreshTime, mPendingEvents, false); // We don't actually dispatch the mPendingEvents now. We'll either // dispatch them the next time we get a refresh driver notification // or the next time somebody calls // nsPresShell::FlushPendingNotifications. if (!mPendingEvents.IsEmpty()) { mPresContext->Document()->SetNeedStyleFlush(); } } return GetAnimationRule(aElement, aStyleContext->GetPseudoType()); }