Closed Bug 1181392 Opened 9 years ago Closed 9 years ago

Try to replace IsFinishedTransition with simply recognizing irrelevant transitions

Categories

(Core :: DOM: Animation, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla42
Tracking Status
firefox42 --- fixed

People

(Reporter: birtles, Assigned: birtles)

References

Details

Attachments

(10 files, 12 obsolete files)

3.90 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
2.01 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
2.32 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
2.02 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
15.13 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
3.47 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
3.26 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
2.04 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
8.35 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
3.65 KB, patch
dbaron
: review+
Details | Diff | Splinter Review
Transitions have an extra "finished transition" state which we use to keep transitions around but ignore them for most purposes. I find this confusing. It's unique to transitions yet we have to check this flag all over the codebase and it has led to bugs where we've failed to update this state. I wonder, now that animations have an idle state, if we can use this idels state represent finished transitions. I'm not certain that this is possible but, if it is, I think it could simplify our code considerably and help bring animations and transitions a little closer together.
Summary: Try to replace IsFinishedTransition with simply marking transitions as idle → Try to replace IsFinishedTransition with simply recognizing irrelevant transitions
Very much WIP, doesn't even compile. I started working on this but I realized I need bug 1180125 to land first. At first I tried cancelling finished transitions but I think from an API and DevTools point of view that doesn't make sense. I think you'd expect the transitions to be there but just be finished such that you can rewind them. What that suggests, then, is that we need to make all the update the call sites of IsFinishedTransition to check one or more of the following conditions: anim->HasCurrentEffect() -- means the animation has an effect and hasn't finished yet anim->IsInEffect() -- means the animation is actually affecting style For a finished transition, neither of those should be true but in some cases we want to test for one or the other or both. By making this change, we should actually fix correctness in the general case (not just transitions). The part where I got stuck is dispatching transitionend events where we need a flag to recognize a newly finished transition. Once bug 1180125 lands that flag will be readily available.
Assignee: nobody → bbirtles
Status: NEW → ASSIGNED
Depends on: 1180125
(In reply to Brian Birtles (:birtles) from comment #1) > The part where I got stuck is dispatching transitionend events where we need > a flag to recognize a newly finished transition. Once bug 1180125 lands that > flag will be readily available. It turns out this doesn't quite give us what we need. * mPreviousFinishedState lets us recognize changes to the finished state in between calls to Animation::UpdateTiming. * mFinishedAtLastComposeStyle lets us recognize changes to the finished state in between calls to Animation::ComposeStyle. For dispatching events, however, we need to recognize changes to the finished state between calls to Animation::Tick. Furthermore, in order to use this in Animation::CanThrottle (in order to do away with mFinishedAtLastComposeStyle and be able to detect newly idle animations etc.) we'd currently need to store *two* flags--one for the latest play state during the previous tick, and one for the current tick. That seems really clumsy. Longer-term we might end up doing something quite different such as: * Requesting restyle from within a tick--that would allow us to get rid of CanThrottle altogether and explicitly request an unthrottled sample when we detect a change requiring it. This might happen fairly soon as part of the work on bug 1151731. * Store the current tick's timeline time on the Animation. This would actually simplify things the most since at the start of a call to Tick() we could simply recalculate the play state with the old time and store it making it easy to detect changes in state throughout the lifetime of the tick. I'm not quite sure what to do in the interim, however. The good news is the patches I have in progress for this might fix a number of perf and correctness issues.
Previously we used IsFinishedTransition so that if the only animations present are finished transitions we could throttle the tick. It turns out we can generalize this further and throttle ticks for all finished animations that are not newly finished, regardless of whether they are running on the compositor or not (although this method won't be called unless the animation property could be run on the compositor anyway). This method is unfortunately quite confusing. For one, it is not strictly limited to animations that are running on the compositor. It purports to only return true when the animation is running on the compositor but the mIsRunningOnCompositor flag doesn't get cleared when the animation finishes (bug 1151694). As a result this method also deals with animations that are now running on the main thread. I'm not sure why it ends up optimizing such animations (by making sure we throttle finished animations running on the main thread) but so long as we're doing that, we should do it properly. This patch reworks this method so that it's hopefully a little easier to follow and a little more consistent since I spent several hours trying to understand the different combinations of inputs this method could take and what question it was trying to answer.
Animation::ComposeStyle uses IsFinishedTransition to skip doing work for transitions that have run their course. We can, however, generalize this to cover all animations that are not currently contributing to the animated style--that is animations that are not "in effect". We need to add this check *after* we update aNeedsRefreshes since an animation that is not "in effect" because it has a delay and no backwards fill (in this case it will have a play state of "running") still needs refreshes.
KeyframeEffectReadOnly uses IsFinishedTransition to exclude finished transitions from certain tests. In the case of IsInPlay and IsCurrent, this call is redundant since finished transitions should already be in the finished state. For KeyframeEffectReadOnly::IsInEffect(), this check is also redundant since finished transitions have no forwards fill and hence the calculated progress value will be null.
GetMinAndMaxScaleForAnimationProperty in nsLayoutUtils uses IsFinishedTransition to ignore finished transitions since they should not have any effect on current or future scale values. We can generalize this, however, and say we are only interested in animations that are *either*: a) running or scheduled to run in the future, i.e. "current", OR b) applying a value, including a finished animation with a forwards fill, i.e. "in effect" Elsewhere, animations that fulfil *either* of this conditions are referred to as "relevant animations" so we can simply test for relevance in this function.
AnimationCollection::HasAnimationOfProperty uses IfFinishedTransition to filter out transitions that should otherwise be ignored. This is used in the following places: 1. nsLayoutUtils::HasAnimations The is only used by nsIFrame::BuildDisplayListForStackingContext to see if there are any opacity animations For this case, simply returning *current* animations would be sufficient (since finished but filling animations should have already filled in the display opacity) 2. CommonAnimationManager::GetAnimationsForCompositor This should really only return *current* animations--that is, animations that are running or scheduled to run. Finished animations never run on the compositor. Indeed, only *playing* animations run on the compositor but, as we will see in some of the cases below, it is sometimes useful to know that an animation *will* run on the compositor in the near future (e.g. so we can pre-render content). The places where GetAnimationsForCompositor is used are: - When building layers to add animations to layers in nsDisplayList--in this case we skip any animations that aren't playing so if GetAnimationsForCompositor only returned current animations that would be more than sufficient. - In nsLayoutUtils::HasAnimationsForCompositor. This in turn is used: - In ChooseScaleAndSetTransform to see if the transform is being animated on the compositor. If so, it calls nsLayoutUtils::ComputeSuitableScaleForAnimation (which also calls GetAnimationsForCompositor) and passes the result to GetMinAndMaxScaleForAnimationProperty which we have already adjusted in part 4 of this patch series to only deal with *relevant* animations Relevant animations include both current animations and in effect animations but we don't run forwards-filling animations on the compositor so GetAnimationsForCompositor should NOT return them. Current should be enough. In fact, playing should be enough but we might want to pre-render layers at a suitable size during their delay phase so current is probably ok. - In nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay to add a fuzz factor to the overflow rect for frames undergoing a transform animation on the compositor. In this case too current should be sufficient. - In nsDisplayOpacity::NeedsActiveLayer to say "yes" if we are animating opacity on the compositor. Presumably in this case it would be good to say "yes" if the animation is in the delay phase too. After the animation is finished, we should drop the layer, i.e. current should be sufficient. - In nsDisplayTransform::ShouldPrerenderTransformedContent. As with nsDisplayOpacity::NeedsActiveLayer, we only need to pre-render transformed content for animations that are current. - In nsDisplayTransform::GetLayerState. As with nsDisplayOpacity::NeedsActiveLayer, we only need to return active here for current animations. - In nsIFrame::IsTransformed. Here we test the display style to see if there is a transform and also check if transform is being animated on the compositor. As a result, we really only need HasAnimationsForCompositor to return true for animations that are playing--otherwise the display style will tell us if we're transformed or not. Returning true for all current compositor animations (which is a superset of playing), however, should not cause problems (we already return true for even more than that). - In nsIFrame::HasOpacityInternal which is much the same as nsIFrame::IsTransformed and hence current should be fine. 3. AnimationCollection::CanThrottleAnimation Here, HasAnimationOfProperty is used when looking for animations that would disqualify us from throttling the animation by having an out-of-date layer generation or being a transform animation that affects scroll and so requires that we do the occasional main thread sample to update scrollbars. It would seem like current animations are enough here too. One interesting case is where we *had* a compositor animation but it has finished or been cancelled. In that case, the animation won't be current and we should not throttle the animation since we need to take it off its layer. It turns out checking for current animations is still ok in this case too. The reasoning is as follows: - If the animation is newly finished, we'll pick that up in Animation::CanThrottle and return false then. - If the animation is newly idle then there are two cases: If the cancelled animation was the only compositor animation then AnimationCollection::CanPerformOnCompositorThread will notice that there are no playing compositor animations and return false and AnimationCollection::CanThrottleAnimation will never be called. If there are other compositor animations running, then AnimationCollection::CanThrottleAnimation will still return false because whatever cancelled the animation will update the animation generation and we'll notice the mismatch between the layer animation generation and the animation generation on the collection.
This patch introduces a separate flag to CSSTransition for tracking if a transition is newly-finished so we can correctly dispatch the transitionend event after removing the IsFinished method and corresponding flag. Note that Animation already has mIsPreviousStateFinished and mFinishedAtLastComposeStyle which would appear to be similar however, - mIsPreviousStateFinished will be removed in bug 1178665 and is updated more often than we queue events so it is not useful here. - mFinishedAtLastComposeStyle is used to determine if we can throttle a style update and is also updated more frequently than we queue events and hence can't be used here. Once we guarantee one call to Tick() per frame we may be able to simplify this by tracking "state on last tick" but for now we need this additional flag on CSSTransition. CSSAnimation has a similar flag for this (mPreviousPhaseOrItertaion) which we may be able to unify at the same point.
Of particular note, this patch removes the check for finished transitions in the case when we can't animate the current change. This case occurs when either we have a non-transition style change that happens to coincide with the current transition value, or a style change to something we can't interpolate to. In either case it's not clear why we would cancel the existing transition only if it is still in motion and not if it is finished. It seems like it should be cancelled in either case and hence this patch removes this check. The other change relates to detecting reversing transitions. In this case we do need to distinguish between transitions that have finished and those that have not. For this purpose, checking if the animation is current should be sufficient. (Note that comparing for PlayState() == Finished would not be enough since we want to also exclude *idle*, i.e. cancelled, animations too.)
Attachment #8633335 - Attachment is obsolete: true
Attachment #8638407 - Attachment is obsolete: true
Attachment #8638408 - Attachment is obsolete: true
Attachment #8638409 - Attachment is obsolete: true
Attachment #8638410 - Attachment is obsolete: true
Attachment #8638411 - Attachment is obsolete: true
Attachment #8638413 - Attachment is obsolete: true
Attachment #8638414 - Attachment is obsolete: true
Attachment #8638415 - Attachment is obsolete: true
Attachment #8638416 - Attachment is obsolete: true
Attachment #8638417 - Attachment is obsolete: true
Attachment #8638412 - Attachment is obsolete: true
Blocks: 1151731
Blocks: 1188251
Comment on attachment 8639145 [details] [diff] [review] part 1 - Remove use of IsFinishedTransition from Animation::CanThrottle >Previously we used IsFinishedTransition so that if the only animations present >are finished transitions we could throttle the tick. It turns out we can >generalize this further and throttle ticks for all finished animations >that are not newly finished, regardless of whether they are running on the >compositor or not (although this method won't be called unless the animation >property could be run on the compositor anyway). In general, I feel like we shouldn't even be calling CanThrottle if mNeedsRefreshes is false, which should cover some of these cases. That said, it doesn't seem to be harmful for it to say that we can throttle when mNeedsRefreshes is false. >+ // Finished animations can be throttled unless this is the first >+ // sample since finishing. In that case we need an unthrottled sample >+ // so we can apply the correct end-of-animation behavior on the main >+ // thread (either removing the animation style or applying the fill mode). >+ if (PlayState() == AnimationPlayState::Finished) { >+ return mFinishedAtLastComposeStyle; > } > >+ // We should also ignore animations which are not "in effect"--i.e. not >+ // producing an output. This will include animations that have finished >+ // and don't apply a forwards fill as well as animations that are idle >+ // or have yet to start. This comment seems misleading given the previous test of PlayState() == Finished; those cases won't get to here. >+ // >+ // Note that unlike newly-finished animations, we don't need to worry about >+ // special handling for newly-idle animations or animations that are newly >+ // yet-to-start since any operation that would cause that change (e.g. a call >+ // to cancel() on the animation, or seeking its current time) will trigger an >+ // unthrottled sample. >+ if (!IsInEffect()) { > return true; > } r=dbaron with that fixed (although I suppose I should think about how we can extend this to doing bug 1105509)
Attachment #8639145 - Flags: review?(dbaron) → review+
Comment on attachment 8639146 [details] [diff] [review] part 2 - Remove use of IsFinishedTransition from Animation::ComposeStyle I assume something guarantees that finished transitions always return a PlayState() of Finished? It certainly makes sense that they should. Is the code in Animation::PlayState good enough to guarantee that? r=dbaron
Attachment #8639146 - Flags: review?(dbaron) → review+
Comment on attachment 8639147 [details] [diff] [review] part 3 - Remove use of IsFinishedTransition in KeyframeEffectReadOnly I guess this commit message answers my questions from the previous patch.
Attachment #8639147 - Flags: review?(dbaron) → review+
Comment on attachment 8639149 [details] [diff] [review] part 5 - Remove use of IsFinishedTransition from AnimationCollection::HasAnimationOfProperty At some point I'd like to have fewer of these has*animations methods, but this definitely seems like an improvement. I think bullet (2) in the commit message could have been shorter, but no point trying to shorten it now. r=dbaron
Attachment #8639149 - Flags: review?(dbaron) → review+
Comment on attachment 8639151 [details] [diff] [review] part 7 - Remove use of IsFinishedTransition from nsTransitionManager::ConsiderStartingTransition Yeah, I probably should have made the first change in this patch when I made retention of finished transitions permanent. I'm not sure I follow the parenthetical comment in the second paragraph of the commit message, though. r=dbaron
Attachment #8639151 - Flags: review?(dbaron) → review+
Comment on attachment 8639152 [details] [diff] [review] part 8 - Remove use of IsFinishedTransition from nsTransitionManager::PruneCompletedTransitions >This patch generalizes the logic in >nsTransitionManager::PruneCompletedTransitions to consider all transitions that >are no longer current (i.e. not running or scheduled to run) rather than >those marked as finished. That's a state they can get into via the Web animations API, but not via transitions, right?
Attachment #8639152 - Flags: review?(dbaron) → review+
(In reply to David Baron [:dbaron] ⏰UTC-7 (busy Aug. 8-Aug. 30) from comment #30) > Comment on attachment 8639151 [details] [diff] [review] > part 7 - Remove use of IsFinishedTransition from > nsTransitionManager::ConsiderStartingTransition > > Yeah, I probably should have made the first change in this patch when I made > retention of finished transitions permanent. > > I'm not sure I follow the parenthetical comment in the second paragraph of > the commit message, though. This is basically saying that when considering if an a new transition reverses an existing one, we want to ignore the existing transition if it is *either* finished (i.e. it ran its course and completed normally), or idle (i.e. script called cancel() at some point disabling the animation). The patch adds a check for HasCurrentEffect() since that will cause us to ignore both cases, whereas simply testing the PlayState() against 'Finished' would only detect the first case.
(In reply to David Baron [:dbaron] ⏰UTC-7 (busy Aug. 8-Aug. 30) from comment #31) > Comment on attachment 8639152 [details] [diff] [review] > part 8 - Remove use of IsFinishedTransition from > nsTransitionManager::PruneCompletedTransitions > > >This patch generalizes the logic in > >nsTransitionManager::PruneCompletedTransitions to consider all transitions that > >are no longer current (i.e. not running or scheduled to run) rather than > >those marked as finished. > > That's a state they can get into via the Web animations API, but not via > transitions, right? Yes, that's right. They will only enter the idle state via the Web animations API. More precisely, they *can* enter the idle state if a style update cancels the animation (e.g. elem.style.transitionProperty = '') but only script using the Web animations API to hold on to the animation object would ever see it in such a state; it will be disassociated from the nsTransitionManager by that point.
Comment on attachment 8639153 [details] [diff] [review] part 9 - Remove use of IsFinishedTransition from nsTransitionManager::FlushTransitions Took me a little time to convince myself that this was ok, but I'm happy now. I wonder if it would be clearer to write this loop differently: >+ // Look for any transitions that can't be throttled because they >+ // may be starting or stopping >+ for (auto iter = collection->mAnimations.cbegin(); >+ canThrottleTick && iter != collection->mAnimations.cend(); >+ ++iter) { >+ canThrottleTick &= (*iter)->CanThrottle(); >+ } to make it more noticeable that it breaks out in the middle when canThrottleTick is false. Maybe it's ok as-is, though.
Attachment #8639153 - Flags: review?(dbaron) → review+
Comment on attachment 8639154 [details] [diff] [review] part 10 - Remove KeyframeEffect::IsFinished >Bug 1181392 part 10 - Remove KeyframeEffect::IsFinished IsFinishedTransition
Attachment #8639154 - Flags: review?(dbaron) → review+
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: