EffectSet EffectCategory::GetEffects(int type) const { EffectSet result; EffectSet::const_iterator iter; for (iter = mEffects.begin(); iter != mEffects.end(); ++iter) { int g = (*iter)->GetEffectFlags(); if ((g & type) == g) result.insert(*iter); } return result; }
// Return all the effects that belong to this immediate category or any // of its subcategories), filtered by effect type. EffectSet EffectCategory::GetAllEffects(int type) const { EffectSet result = GetEffects(type); CategorySet::const_iterator iter; for (iter = mSubCategories.begin(); iter != mSubCategories.end(); ++iter) { EffectSet tmp = (*iter)->GetAllEffects(type); EffectSet::const_iterator itr2; for (itr2 = tmp.begin(); itr2 != tmp.end(); ++itr2) result.insert(*itr2); } return result; }
void KeyframeEffectReadOnly::UnregisterTarget() { EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); if (effectSet) { effectSet->RemoveEffect(*this); if (effectSet->IsEmpty()) { EffectSet::DestroyEffectSet(mTarget->mElement, mTarget->mPseudoType); } } }
bool KeyframeEffectReadOnly::CanThrottleTransformChanges(nsIFrame& aFrame) const { // If we know that the animation cannot cause overflow, // we can just disable flushes for this animation. // If we don't show scrollbars, we don't care about overflow. if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) { return true; } nsPresContext* presContext = GetPresContext(); // CanThrottleTransformChanges is only called as part of a refresh driver tick // in which case we expect to has a pres context. MOZ_ASSERT(presContext); TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh(); EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); MOZ_ASSERT(effectSet, "CanThrottleTransformChanges is expected to be called" " on an effect in an effect set"); MOZ_ASSERT(mAnimation, "CanThrottleTransformChanges is expected to be called" " on an effect with a parent animation"); TimeStamp animationRuleRefreshTime = effectSet->AnimationRuleRefreshTime(mAnimation->CascadeLevel()); // If this animation can cause overflow, we can throttle some of the ticks. if (!animationRuleRefreshTime.IsNull() && (now - animationRuleRefreshTime) < OverflowRegionRefreshInterval()) { return true; } // If the nearest scrollable ancestor has overflow:hidden, // we don't care about overflow. nsIScrollableFrame* scrollable = nsLayoutUtils::GetNearestScrollableFrame(&aFrame); if (!scrollable) { return true; } ScrollbarStyles ss = scrollable->GetScrollbarStyles(); if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN && ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN && scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) { return true; } return false; }
void KeyframeEffectReadOnly::MarkCascadeNeedsUpdate() { if (!mTarget) { return; } EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); if (!effectSet) { return; } effectSet->MarkCascadeNeedsUpdate(); }
EffectSet EffectManager::GetUnsortedEffects(int flags) const { if (flags == ALL_EFFECTS) return *mUnsorted; EffectSet result; EffectSet::const_iterator iter; for (iter = mUnsorted->begin(); iter != mUnsorted->end(); ++iter) { int g = (*iter)->GetEffectFlags(); if ((flags & g) == g) result.insert(*iter); } return result; }
/* static */ void EffectCompositor::MaybeUpdateCascadeResults(Element* aElement, nsCSSPseudoElements::Type aPseudoType, nsStyleContext* aStyleContext) { EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType); if (!effects || !effects->CascadeNeedsUpdate()) { return; } UpdateCascadeResults(*effects, aElement, aPseudoType, aStyleContext); MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Failed to update cascade state"); }
/* static */ void EffectSet::DestroyEffectSet( dom::Element* aElement, CSSPseudoElementType aPseudoType) { nsAtom* propName = GetEffectSetPropertyAtom(aPseudoType); EffectSet* effectSet = static_cast<EffectSet*>(aElement->GetProperty(propName)); if (!effectSet) { return; } MOZ_ASSERT(!effectSet->IsBeingEnumerated(), "Should not destroy an effect set while it is being enumerated"); effectSet = nullptr; aElement->DeleteProperty(propName); }
bool KeyframeEffectReadOnly::ShouldBlockAsyncTransformAnimations( const nsIFrame* aFrame, AnimationPerformanceWarning::Type& aPerformanceWarning) const { // We currently only expect this method to be called for effects whose // animations are eligible for the compositor since, Animations that are // paused, zero-duration, finished etc. should not block other animations from // running on the compositor. MOZ_ASSERT(mAnimation && mAnimation->IsPlayableOnCompositor()); EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); for (const AnimationProperty& property : mProperties) { // If there is a property for animations level that is overridden by // !important rules, it should not block other animations from running // on the compositor. // NOTE: We don't currently check for !important rules for properties that // don't run on the compositor. As result such properties (e.g. margin-left) // can still block async animations even if they are overridden by // !important rules. if (effectSet && effectSet->PropertiesWithImportantRules() .HasProperty(property.mProperty) && effectSet->PropertiesForAnimationsLevel() .HasProperty(property.mProperty)) { continue; } // Check for geometric properties if (IsGeometricProperty(property.mProperty)) { aPerformanceWarning = AnimationPerformanceWarning::Type::TransformWithGeometricProperties; return true; } // Check for unsupported transform animations if (property.mProperty == eCSSProperty_transform) { if (!CanAnimateTransformOnCompositor(aFrame, aPerformanceWarning)) { return true; } } } return false; }
/* static */ void EffectCompositor::ComposeAnimationRule(dom::Element* aElement, nsCSSPseudoElements::Type aPseudoType, CascadeLevel aCascadeLevel, bool& aStyleChanging) { EffectSet* effects = EffectSet::GetEffectSet(aElement, aPseudoType); if (!effects) { return; } // The caller is responsible for calling MaybeUpdateCascadeResults first. MOZ_ASSERT(!effects->CascadeNeedsUpdate(), "Animation cascade out of date when composing animation rule"); // Get a list of effects for the current level sorted by composite order. nsTArray<KeyframeEffectReadOnly*> sortedEffectList; for (KeyframeEffectReadOnly* effect : *effects) { MOZ_ASSERT(effect->GetAnimation()); if (effect->GetAnimation()->CascadeLevel() == aCascadeLevel) { sortedEffectList.AppendElement(effect); } } sortedEffectList.Sort(EffectCompositeOrderComparator()); RefPtr<AnimValuesStyleRule>& animationRule = effects->AnimationRule(aCascadeLevel); animationRule = nullptr; // We'll set aStyleChanging to true below if necessary. aStyleChanging = false; // If multiple animations specify behavior for the same property the // animation with the *highest* composite order wins. // As a result, we iterate from last animation to first and, if a // property has already been set, we don't change it. nsCSSPropertySet properties; for (KeyframeEffectReadOnly* effect : Reversed(sortedEffectList)) { effect->GetAnimation()->ComposeStyle(animationRule, properties, aStyleChanging); } }
void KeyframeEffectReadOnly::UpdateTargetRegistration() { if (!mTarget) { return; } bool isRelevant = mAnimation && mAnimation->IsRelevant(); // Animation::IsRelevant() returns a cached value. It only updates when // something calls Animation::UpdateRelevance. Whenever our timing changes, // we should be notifying our Animation before calling this, so // Animation::IsRelevant() should be up-to-date by the time we get here. MOZ_ASSERT(isRelevant == IsCurrent() || IsInEffect(), "Out of date Animation::IsRelevant value"); if (isRelevant) { EffectSet* effectSet = EffectSet::GetOrCreateEffectSet(mTarget->mElement, mTarget->mPseudoType); effectSet->AddEffect(*this); } else { UnregisterTarget(); } }
const AnimationProperty* KeyframeEffectReadOnly::GetEffectiveAnimationOfProperty( nsCSSPropertyID aProperty) const { EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); for (size_t propIdx = 0, propEnd = mProperties.Length(); propIdx != propEnd; ++propIdx) { if (aProperty == mProperties[propIdx].mProperty) { const AnimationProperty* result = &mProperties[propIdx]; // Skip if there is a property of animation level that is overridden // by !important rules. if (effectSet && effectSet->PropertiesWithImportantRules() .HasProperty(result->mProperty) && effectSet->PropertiesForAnimationsLevel() .HasProperty(result->mProperty)) { result = nullptr; } return result; } } return nullptr; }
bool KeyframeEffectReadOnly::CanThrottle() const { // Unthrottle if we are not in effect or current. This will be the case when // our owning animation has finished, is idle, or when we are in the delay // phase (but without a backwards fill). In each case the computed progress // value produced on each tick will be the same so we will skip requesting // unnecessary restyles in NotifyAnimationTimingUpdated. Any calls we *do* get // here will be because of a change in state (e.g. we are newly finished or // newly no longer in effect) in which case we shouldn't throttle the sample. if (!IsInEffect() || !IsCurrent()) { return false; } nsIFrame* frame = GetAnimationFrame(); if (!frame) { // There are two possible cases here. // a) No target element // b) The target element has no frame, e.g. because it is in a display:none // subtree. // In either case we can throttle the animation because there is no // need to update on the main thread. return true; } // We can throttle the animation if the animation is paint only and // the target frame is out of view or the document is in background tabs. if (CanIgnoreIfNotVisible()) { nsIPresShell* presShell = GetPresShell(); if ((presShell && !presShell->IsActive()) || frame->IsScrolledOutOfView()) { return true; } } // First we need to check layer generation and transform overflow // prior to the property.mIsRunningOnCompositor check because we should // occasionally unthrottle these animations even if the animations are // already running on compositor. for (const LayerAnimationInfo::Record& record : LayerAnimationInfo::sRecords) { // Skip properties that are overridden in the cascade. // (GetAnimationOfProperty, as called by HasAnimationOfProperty, // only returns an animation if it currently wins in the cascade.) if (!HasAnimationOfProperty(record.mProperty)) { continue; } EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType); MOZ_ASSERT(effectSet, "CanThrottle should be called on an effect " "associated with a target element"); layers::Layer* layer = FrameLayerBuilder::GetDedicatedLayer(frame, record.mLayerType); // Unthrottle if the layer needs to be brought up to date if (!layer || effectSet->GetAnimationGeneration() != layer->GetAnimationGeneration()) { return false; } // If this is a transform animation that affects the overflow region, // we should unthrottle the animation periodically. if (record.mProperty == eCSSProperty_transform && !CanThrottleTransformChanges(*frame)) { return false; } } for (const AnimationProperty& property : mProperties) { if (!property.mIsRunningOnCompositor) { return false; } } return true; }
nsIStyleRule* nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext, mozilla::dom::Element* aElement) { // Ignore animations for print or print preview, and for elements // that are not attached to the document tree. if (!mPresContext->IsDynamic() || !aElement->IsInComposedDoc()) { 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(); AnimationCollection* collection = GetAnimationCollection(aElement, aStyleContext->GetPseudoType(), false /* aCreateIfNeeded */); if (!collection && disp->mAnimationNameCount == 1 && disp->mAnimations[0].GetName().IsEmpty()) { return nullptr; } nsAutoAnimationMutationBatch mb(aElement->OwnerDoc()); // build the animations list dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline(); AnimationPtrArray newAnimations; if (!aStyleContext->IsInDisplayNoneSubtree()) { BuildAnimations(aStyleContext, aElement, timeline, newAnimations); } if (newAnimations.IsEmpty()) { if (collection) { collection->Destroy(); } return nullptr; } if (collection) { collection->mStyleRuleRefreshTime = TimeStamp(); EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aStyleContext->GetPseudoType()); if (effectSet) { effectSet->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 (!collection->mAnimations.IsEmpty()) { for (size_t newIdx = newAnimations.Length(); newIdx-- != 0;) { Animation* newAnim = newAnimations[newIdx]; // Find the matching animation with this name in the old list // of animations. We iterate through both lists in a backwards // direction which means that if there are more animations in // the new list of animations with a given name than in the old // list, it will be the animations towards the of the beginning of // the list that do not match and are treated as new animations. RefPtr<CSSAnimation> oldAnim; size_t oldIdx = collection->mAnimations.Length(); while (oldIdx-- != 0) { CSSAnimation* a = collection->mAnimations[oldIdx]->AsCSSAnimation(); MOZ_ASSERT(a, "All animations in the CSS Animation collection should" " be CSSAnimation objects"); if (a->AnimationName() == newAnim->AsCSSAnimation()->AnimationName()) { oldAnim = a; break; } } if (!oldAnim) { // FIXME: Bug 1134163 - We shouldn't queue animationstart events // until the animation is actually ready to run. However, we // currently have some tests that assume that these events are // dispatched within the same tick as the animation is added // so we need to queue up any animationstart events from newly-created // animations. newAnim->AsCSSAnimation()->QueueEvents(); continue; } bool animationChanged = false; // Update the old from the new so we can keep the original object // identity (and any expando properties attached to it). if (oldAnim->GetEffect() && newAnim->GetEffect()) { KeyframeEffectReadOnly* oldEffect = oldAnim->GetEffect(); KeyframeEffectReadOnly* newEffect = newAnim->GetEffect(); animationChanged = oldEffect->Timing() != newEffect->Timing() || oldEffect->Properties() != newEffect->Properties(); oldEffect->SetTiming(newEffect->Timing()); // To preserve the mIsRunningOnCompositor value on each property, // we copy it from the old effect to the new effect since, in the // following step, we will completely clobber the properties on the // old effect with the values on the new effect. CopyIsRunningOnCompositor(*oldEffect, *newEffect); oldEffect->Properties() = newEffect->Properties(); } // Handle changes in play state. If the animation is idle, however, // changes to animation-play-state should *not* restart it. if (oldAnim->PlayState() != AnimationPlayState::Idle) { // CSSAnimation takes care of override behavior so that, // for example, if the author has called pause(), that will // override the animation-play-state. // (We should check newAnim->IsStylePaused() but that requires // downcasting to CSSAnimation and we happen to know that // newAnim will only ever be paused by calling PauseFromStyle // making IsPausedOrPausing synonymous in this case.) if (!oldAnim->IsStylePaused() && newAnim->IsPausedOrPausing()) { oldAnim->PauseFromStyle(); animationChanged = true; } else if (oldAnim->IsStylePaused() && !newAnim->IsPausedOrPausing()) { oldAnim->PlayFromStyle(); animationChanged = true; } } oldAnim->CopyAnimationIndex(*newAnim->AsCSSAnimation()); // Updating the effect timing above might already have caused the // animation to become irrelevant so only add a changed record if // the animation is still relevant. if (animationChanged && oldAnim->IsRelevant()) { nsNodeUtils::AnimationChanged(oldAnim); } // Replace new animation with the (updated) old one and remove the // old one from the array so we don't try to match it any more. // // Although we're doing this while iterating this is safe because // we're not changing the length of newAnimations and we've finished // iterating over the list of old iterations. newAnim->CancelFromStyle(); newAnim = nullptr; newAnimations.ReplaceElementAt(newIdx, oldAnim); collection->mAnimations.RemoveElementAt(oldIdx); } } } else { collection = GetAnimationCollection(aElement, aStyleContext->GetPseudoType(), true /* aCreateIfNeeded */); for (Animation* animation : newAnimations) { // FIXME: Bug 1134163 - As above, we have shouldn't actually need to // queue events here. (But we do for now since some tests expect // animationstart events to be dispatched immediately.) animation->AsCSSAnimation()->QueueEvents(); } } collection->mAnimations.SwapElements(newAnimations); collection->mStyleChanging = true; // Cancel removed animations for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) { newAnimations[newAnimIdx]->CancelFromStyle(); } EffectCompositor::UpdateCascadeResults(aElement, aStyleContext->GetPseudoType(), aStyleContext); TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); collection->EnsureStyleRuleFor(refreshTime); // We don't actually dispatch the pending events now. We'll either // dispatch them the next time we get a refresh driver notification // or the next time somebody calls // nsPresShell::FlushPendingNotifications. if (mEventDispatcher.HasQueuedEvents()) { mPresContext->Document()->SetNeedStyleFlush(); } return GetAnimationRule(aElement, aStyleContext->GetPseudoType()); }
// Helper function to factor out the common logic from // GetAnimationsForCompositor and HasAnimationsForCompositor. // // Takes an optional array to fill with eligible animations. // // Returns true if there are eligible animations, false otherwise. bool FindAnimationsForCompositor(const nsIFrame* aFrame, nsCSSProperty aProperty, nsTArray<RefPtr<dom::Animation>>* aMatches /*out*/) { MOZ_ASSERT(!aMatches || aMatches->IsEmpty(), "Matches array, if provided, should be empty"); EffectSet* effects = EffectSet::GetEffectSet(aFrame); if (!effects || effects->IsEmpty()) { return false; } if (aFrame->RefusedAsyncAnimation()) { return false; } // The animation cascade will almost always be up-to-date by this point // but there are some cases such as when we are restoring the refresh driver // from test control after seeking where it might not be the case. // // Those cases are probably not important but just to be safe, let's make // sure the cascade is up to date since if it *is* up to date, this is // basically a no-op. Maybe<Pair<dom::Element*, nsCSSPseudoElements::Type>> pseudoElement = EffectCompositor::GetAnimationElementAndPseudoForFrame(aFrame); if (pseudoElement) { EffectCompositor::MaybeUpdateCascadeResults(pseudoElement->first(), pseudoElement->second(), aFrame->StyleContext()); } if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) { if (nsLayoutUtils::IsAnimationLoggingEnabled()) { nsCString message; message.AppendLiteral("Performance warning: Async animations are " "disabled"); AnimationUtils::LogAsyncAnimationFailure(message); } return false; } bool foundSome = false; for (KeyframeEffectReadOnly* effect : *effects) { MOZ_ASSERT(effect && effect->GetAnimation()); Animation* animation = effect->GetAnimation(); if (!animation->IsPlaying()) { continue; } if (effect->ShouldBlockCompositorAnimations(aFrame)) { if (aMatches) { aMatches->Clear(); } return false; } if (!effect->HasAnimationOfProperty(aProperty)) { continue; } if (aMatches) { aMatches->AppendElement(animation); } foundSome = true; } MOZ_ASSERT(!foundSome || !aMatches || !aMatches->IsEmpty(), "If return value is true, matches array should be non-empty"); return foundSome; }
/* static */ void EffectCompositor::UpdateCascadeResults(EffectSet& aEffectSet, Element* aElement, nsCSSPseudoElements::Type aPseudoType, nsStyleContext* aStyleContext) { MOZ_ASSERT(EffectSet::GetEffectSet(aElement, aPseudoType) == &aEffectSet, "Effect set should correspond to the specified (pseudo-)element"); if (aEffectSet.IsEmpty()) { aEffectSet.MarkCascadeUpdated(); return; } // Get a list of effects sorted by composite order. nsTArray<KeyframeEffectReadOnly*> sortedEffectList; for (KeyframeEffectReadOnly* effect : aEffectSet) { sortedEffectList.AppendElement(effect); } sortedEffectList.Sort(EffectCompositeOrderComparator()); // Get properties that override the *animations* level of the cascade. // // We only do this for properties that we can animate on the compositor // since we will apply other properties on the main thread where the usual // cascade applies. nsCSSPropertySet overriddenProperties; if (aStyleContext) { GetOverriddenProperties(aStyleContext, aEffectSet, overriddenProperties); } bool changed = false; nsCSSPropertySet animatedProperties; // Iterate from highest to lowest composite order. for (KeyframeEffectReadOnly* effect : Reversed(sortedEffectList)) { MOZ_ASSERT(effect->GetAnimation(), "Effects on a target element should have an Animation"); bool inEffect = effect->IsInEffect(); for (AnimationProperty& prop : effect->Properties()) { bool winsInCascade = !animatedProperties.HasProperty(prop.mProperty) && inEffect; // If this property wins in the cascade, add it to the set of animated // properties. We need to do this even if the property is overridden // (in which case we set winsInCascade to false below) since we don't // want to fire transitions on these properties. if (winsInCascade) { animatedProperties.AddProperty(prop.mProperty); } // For effects that will be applied to the animations level of the // cascade, we need to check that the property isn't being set by // something with higher priority in the cascade. // // We only do this, however, for properties that can be animated on // the compositor. For properties animated on the main thread the usual // cascade ensures these animations will be correctly overridden. if (winsInCascade && effect->GetAnimation()->CascadeLevel() == CascadeLevel::Animations && overriddenProperties.HasProperty(prop.mProperty)) { winsInCascade = false; } if (winsInCascade != prop.mWinsInCascade) { changed = true; } prop.mWinsInCascade = winsInCascade; } } aEffectSet.MarkCascadeUpdated(); // If there is any change in the cascade result, update animations on // layers with the winning animations. nsPresContext* presContext = GetPresContext(aElement); if (changed && presContext) { // We currently unconditionally update both animations and transitions // even if we could, for example, get away with only updating animations. // This is a temporary measure until we unify all animation style updating // under EffectCompositor. AnimationCollection* animations = presContext->AnimationManager()->GetAnimationCollection(aElement, aPseudoType, false); /* don't create */ if (animations) { animations->RequestRestyle(AnimationCollection::RestyleType::Layer); } AnimationCollection* transitions = presContext->TransitionManager()->GetAnimationCollection(aElement, aPseudoType, false); /* don't create */ if (transitions) { transitions->RequestRestyle(AnimationCollection::RestyleType::Layer); } } }