void
nsAnimationManager::MaybeUpdateCascadeResults(AnimationCollection* aCollection)
{
  for (size_t animIdx = aCollection->mAnimations.Length(); animIdx-- != 0; ) {
    CSSAnimation* anim = aCollection->mAnimations[animIdx]->AsCSSAnimation();
    if (anim->IsInEffect() != anim->mInEffectForCascadeResults) {
      // Update our own cascade results.
      mozilla::dom::Element* element = aCollection->GetElementToRestyle();
      bool updatedCascadeResults = false;
      if (element) {
        nsIFrame* frame = element->GetPrimaryFrame();
        if (frame) {
          UpdateCascadeResults(frame->StyleContext(), aCollection);
          updatedCascadeResults = true;
        }
      }

      if (!updatedCascadeResults) {
        // If we don't have a style context we can't do the work of updating
        // cascading results but we need to make sure to update
        // mInEffectForCascadeResults or else we'll keep running this
        // code every time (potentially leading to infinite recursion due
        // to the fact that this method both calls and is (indirectly) called
        // by nsTransitionManager).
        anim->mInEffectForCascadeResults = anim->IsInEffect();
      }

      // Notify the transition manager, whose results might depend on ours.
      mPresContext->TransitionManager()->
        UpdateCascadeResultsWithAnimations(aCollection);

      return;
    }
  }
}
void
nsAnimationManager::QueueEvents(AnimationCollection* aCollection,
                                EventArray& aEventsToDispatch)
{
  for (size_t animIdx = aCollection->mAnimations.Length(); animIdx-- != 0; ) {
    CSSAnimation* anim = aCollection->mAnimations[animIdx]->AsCSSAnimation();
    MOZ_ASSERT(anim, "Expected a collection of CSS Animations");
    anim->QueueEvents(aEventsToDispatch);
  }
}
static void
UpdateOldAnimationPropertiesWithNew(
    CSSAnimation& aOld,
    TimingParams& aNewTiming,
    nsTArray<Keyframe>& aNewKeyframes,
    bool aNewIsStylePaused,
    nsStyleContext* aStyleContext)
{
  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 (aOld.GetEffect()) {
    AnimationEffectReadOnly* oldEffect = aOld.GetEffect();
    animationChanged = oldEffect->SpecifiedTiming() != aNewTiming;
    oldEffect->SetSpecifiedTiming(aNewTiming);

    KeyframeEffectReadOnly* oldKeyframeEffect = oldEffect->AsKeyframeEffect();
    if (oldKeyframeEffect) {
      oldKeyframeEffect->SetKeyframes(Move(aNewKeyframes), aStyleContext);
    }
  }

  // Handle changes in play state. If the animation is idle, however,
  // changes to animation-play-state should *not* restart it.
  if (aOld.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 aNew->IsStylePaused() but that requires
    //  downcasting to CSSAnimation and we happen to know that
    //  aNew will only ever be paused by calling PauseFromStyle
    //  making IsPausedOrPausing synonymous in this case.)
    if (!aOld.IsStylePaused() && aNewIsStylePaused) {
      aOld.PauseFromStyle();
      animationChanged = true;
    } else if (aOld.IsStylePaused() && !aNewIsStylePaused) {
      aOld.PlayFromStyle();
      animationChanged = true;
    }
  }

  // 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 && aOld.IsRelevant()) {
    nsNodeUtils::AnimationChanged(&aOld);
  }
}
Beispiel #4
0
void
nsAnimationManager::MaybeUpdateCascadeResults(AnimationCollection* aCollection)
{
  for (size_t animIdx = aCollection->mAnimations.Length(); animIdx-- != 0; ) {
    CSSAnimation* anim = aCollection->mAnimations[animIdx]->AsCSSAnimation();
    if (anim->IsInEffect() != anim->mInEffectForCascadeResults) {
      // Update our own cascade results.
      mozilla::dom::Element* element = aCollection->GetElementToRestyle();
      if (element) {
        nsIFrame* frame = element->GetPrimaryFrame();
        if (frame) {
          UpdateCascadeResults(frame->StyleContext(), aCollection);
        }
      }

      // Notify the transition manager, whose results might depend on ours.
      mPresContext->TransitionManager()->
        UpdateCascadeResultsWithAnimations(aCollection);

      return;
    }
  }
}
// Find the matching animation by |aName| in the old list
// of animations and remove the matched animation from the list.
static already_AddRefed<CSSAnimation>
PopExistingAnimation(const nsAString& aName,
                     nsAnimationManager::CSSAnimationCollection* aCollection)
{
  if (!aCollection) {
    return nullptr;
  }

  // Animations are stored in reverse order to how they appear in the
  // animation-name property. However, we want to match animations beginning
  // from the end of the animation-name list, so we iterate *forwards*
  // through the collection.
  for (size_t idx = 0, length = aCollection->mAnimations.Length();
       idx != length; ++ idx) {
    CSSAnimation* cssAnim = aCollection->mAnimations[idx];
    if (cssAnim->AnimationName() == aName) {
      RefPtr<CSSAnimation> match = cssAnim;
      aCollection->mAnimations.RemoveElementAt(idx);
      return match.forget();
    }
  }

  return nullptr;
}
bool
CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const
{
  MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
             "Should only be called for CSS animations that are sorted "
             "as CSS animations (i.e. tied to CSS markup)");

  // 0. Object-equality case
  if (&aOther == this) {
    return false;
  }

  // 1. Sort by document order
  if (!mOwningElement.Equals(aOther.mOwningElement)) {
    return mOwningElement.LessThan(aOther.mOwningElement);
  }

  // 2. (Same element and pseudo): Sort by position in animation-name
  return mAnimationIndex < aOther.mAnimationIndex;
}
/* static */ void
nsAnimationManager::UpdateCascadeResults(
                      nsStyleContext* aStyleContext,
                      AnimationCollection* aElementAnimations)
{
  /*
   * Figure out which properties we need to examine.
   */

  // size of 2 since we only currently have 2 properties we animate on
  // the compositor
  nsAutoTArray<nsCSSProperty, 2> propertiesToTrack;

  {
    nsCSSPropertySet propertiesToTrackAsSet;

    for (size_t animIdx = aElementAnimations->mAnimations.Length();
         animIdx-- != 0; ) {
      const Animation* anim = aElementAnimations->mAnimations[animIdx];
      const KeyframeEffectReadOnly* effect = anim->GetEffect();
      if (!effect) {
        continue;
      }

      for (size_t propIdx = 0, propEnd = effect->Properties().Length();
           propIdx != propEnd; ++propIdx) {
        const AnimationProperty& prop = effect->Properties()[propIdx];
        // We only bother setting mWinsInCascade for properties that we
        // can animate on the compositor.
        if (nsCSSProps::PropHasFlags(prop.mProperty,
                                     CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) {
          if (!propertiesToTrackAsSet.HasProperty(prop.mProperty)) {
            propertiesToTrack.AppendElement(prop.mProperty);
            propertiesToTrackAsSet.AddProperty(prop.mProperty);
          }
        }
      }
    }
  }

  /*
   * Determine whether those properties are set in things that
   * override animations.
   */

  nsCSSPropertySet propertiesOverridden;
  nsRuleNode::ComputePropertiesOverridingAnimation(propertiesToTrack,
                                                   aStyleContext,
                                                   propertiesOverridden);

  /*
   * Set mWinsInCascade based both on what is overridden at levels
   * higher than animations and based on one animation overriding
   * another.
   *
   * We iterate from the last animation to the first, just like we do
   * when calling ComposeStyle from AnimationCollection::EnsureStyleRuleFor.
   * Later animations override earlier ones, so we add properties to the set
   * of overridden properties as we encounter them, if the animation is
   * currently in effect.
   */

  bool changed = false;
  for (size_t animIdx = aElementAnimations->mAnimations.Length();
       animIdx-- != 0; ) {
    CSSAnimation* anim =
      aElementAnimations->mAnimations[animIdx]->AsCSSAnimation();
    KeyframeEffectReadOnly* effect = anim->GetEffect();

    anim->mInEffectForCascadeResults = anim->IsInEffect();

    if (!effect) {
      continue;
    }

    for (size_t propIdx = 0, propEnd = effect->Properties().Length();
         propIdx != propEnd; ++propIdx) {
      AnimationProperty& prop = effect->Properties()[propIdx];
      // We only bother setting mWinsInCascade for properties that we
      // can animate on the compositor.
      if (nsCSSProps::PropHasFlags(prop.mProperty,
                                   CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR)) {
        bool newWinsInCascade =
          !propertiesOverridden.HasProperty(prop.mProperty);
        if (newWinsInCascade != prop.mWinsInCascade) {
          changed = true;
        }
        prop.mWinsInCascade = newWinsInCascade;

        if (prop.mWinsInCascade && anim->mInEffectForCascadeResults) {
          // This animation is in effect right now, so it overrides
          // earlier animations.  (For animations that aren't in effect,
          // we set mWinsInCascade as though they were, but they don't
          // suppress animations lower in the cascade.)
          propertiesOverridden.AddProperty(prop.mProperty);
        }
      }
    }
  }

  if (changed) {
    nsPresContext* presContext = aElementAnimations->mManager->PresContext();
    presContext->RestyleManager()->IncrementAnimationGeneration();
    aElementAnimations->UpdateAnimationGeneration(presContext);
    aElementAnimations->PostUpdateLayerAnimations();

    // Invalidate our style rule.
    aElementAnimations->mNeedsRefreshes = true;
    aElementAnimations->mStyleRuleRefreshTime = TimeStamp();
  }
}
nsIStyleRule*
nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
                                       mozilla::dom::Element* aElement)
{
  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();
  AnimationCollection* collection =
    GetAnimations(aElement, aStyleContext->GetPseudoType(), false);
  if (!collection &&
      disp->mAnimationNameCount == 1 &&
      disp->mAnimations[0].GetName().IsEmpty()) {
    return nullptr;
  }

  nsAutoAnimationMutationBatch mb(aElement);

  // 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) {
      // There might be transitions that run now that animations don't
      // override them.
      mPresContext->TransitionManager()->
        UpdateCascadeResultsWithAnimationsToBeDestroyed(collection);

      collection->Destroy();
    }
    return nullptr;
  }

  if (collection) {
    collection->mStyleRule = nullptr;
    collection->mStyleRuleRefreshTime = TimeStamp();
    collection->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.
        nsRefPtr<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->Name() == newAnim->Name()) {
            oldAnim = a;
            break;
          }
        }
        if (!oldAnim) {
          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->Timing() = newEffect->Timing();
          oldEffect->Properties() = newEffect->Properties();
        }

        // Reset compositor state so animation will be re-synchronized.
        oldAnim->ClearIsRunningOnCompositor();

        // 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;
          }
        }

        if (animationChanged) {
          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);

        // We've touched the old animation's timing properties, so this
        // could update the old animation's relevance.
        oldAnim->UpdateRelevance();
      }
    }
  } else {
    collection =
      GetAnimations(aElement, aStyleContext->GetPseudoType(), true);
  }
  collection->mAnimations.SwapElements(newAnimations);
  collection->mNeedsRefreshes = true;
  collection->Tick();

  // Cancel removed animations
  for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) {
    newAnimations[newAnimIdx]->CancelFromStyle();
  }

  UpdateCascadeResults(aStyleContext, collection);

  TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
  UpdateStyleAndEvents(collection, refreshTime,
                       EnsureStyleRule_IsNotThrottled);
  // 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());
}
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());
}