author | Hiroyuki Ikezoe <hiikezoe@mozilla-japan.org> |
Wed, 25 May 2016 05:51:57 +0900 | |
changeset 298901 | 72c16a3d0b0a13a4f05929a326d5fe859a9a3108 |
parent 298900 | 8ea3030332a90003b4a2dff0daa8329cf2109792 |
child 298902 | 05ebd1f677ed3454efc7bb939767dbc45c826792 |
push id | 30285 |
push user | cbook@mozilla.com |
push date | Wed, 25 May 2016 13:06:07 +0000 |
treeherder | mozilla-central@d6d4e8417d2f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | birtles |
bugs | 1167519, 1273834 |
milestone | 49.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -384,16 +384,39 @@ AddAnimationForProperty(nsIFrame* aFrame TransformReferenceBox refBox(aFrame); layers::Animation* animation = aPending ? aLayer->AddAnimationForNextTransaction() : aLayer->AddAnimation(); const TimingParams& timing = aAnimation->GetEffect()->SpecifiedTiming(); + + // If we are starting a new transition that replaces an existing transition + // running on the compositor, it is possible that the animation on the + // compositor will have advanced ahead of the main thread. If we use as + // the starting point of the new transition, the current value of the + // replaced transition as calculated on the main thread using the refresh + // driver time, the new transition will jump when it starts. Instead, we + // re-calculate the starting point of the new transition by applying the + // current TimeStamp to the parameters of the replaced transition. + // + // We need to do this here, rather than when we generate the new transition, + // since after generating the new transition other requestAnimationFrame + // callbacks may run that introduce further lag between the main thread and + // the compositor. + if (aAnimation->AsCSSTransition() && + aAnimation->GetEffect()) { + MOZ_ASSERT(aAnimation->GetEffect()->AsTransition(), + "CSSTransition' effect should be an ElementPropertyTransition " + "until we fix bug 1049975"); + aAnimation->GetEffect()->AsTransition()-> + UpdateStartValueFromReplacedTransition(); + } + const ComputedTiming computedTiming = aAnimation->GetEffect()->GetComputedTiming(); Nullable<TimeDuration> startTime = aAnimation->GetCurrentOrPendingStartTime(); animation->startTime() = startTime.IsNull() ? TimeStamp() : aAnimation->AnimationTimeToTimeStamp( StickyTimeDuration(timing.mDelay)); animation->initialCurrentTime() = aAnimation->GetCurrentTime().Value()
--- a/layout/style/nsTransitionManager.cpp +++ b/layout/style/nsTransitionManager.cpp @@ -65,16 +65,57 @@ ElementPropertyTransition::CurrentValueP "Got a null progress for a fill mode of 'both'"); MOZ_ASSERT(mKeyframes.Length() == 2, "Should have two animation keyframes for a transition"); return ComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction, computedTiming.mProgress.Value(), computedTiming.mBeforeFlag); } +void +ElementPropertyTransition::UpdateStartValueFromReplacedTransition() +{ + if (!mReplacedTransition) { + return; + } + MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(), + CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR), + "The transition property should be able to be run on the " + "compositor"); + MOZ_ASSERT(mTarget && mTarget->mElement->OwnerDoc(), + "We should have a valid document at this moment"); + + dom::DocumentTimeline* timeline = mTarget->mElement->OwnerDoc()->Timeline(); + ComputedTiming computedTiming = GetComputedTimingAt( + dom::CSSTransition::GetCurrentTimeAt(*timeline, + TimeStamp::Now(), + mReplacedTransition->mStartTime, + mReplacedTransition->mPlaybackRate), + mReplacedTransition->mTiming); + + if (!computedTiming.mProgress.IsNull()) { + double valuePosition = + ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction, + computedTiming.mProgress.Value(), + computedTiming.mBeforeFlag); + StyleAnimationValue startValue; + if (StyleAnimationValue::Interpolate(mProperties[0].mProperty, + mReplacedTransition->mFromValue, + mReplacedTransition->mToValue, + valuePosition, startValue)) { + MOZ_ASSERT(mProperties.Length() == 1 && + mProperties[0].mSegments.Length() == 1, + "The transition should have one property and one segment"); + mProperties[0].mSegments[0].mFromValue = Move(startValue); + } + } + + mReplacedTransition.reset(); +} + ////////////////////////// CSSTransition //////////////////////////// JSObject* CSSTransition::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return dom::CSSTransitionBinding::Wrap(aCx, this, aGivenProto); } @@ -199,16 +240,33 @@ CSSTransition::HasLowerCompositeOrderTha return mAnimationIndex < aOther.mAnimationIndex; } // 3. (Same transition generation): Sort by transition property return nsCSSProps::GetStringValue(TransitionProperty()) < nsCSSProps::GetStringValue(aOther.TransitionProperty()); } +/* static */ Nullable<TimeDuration> +CSSTransition::GetCurrentTimeAt(const DocumentTimeline& aTimeline, + const TimeStamp& aBaseTime, + const TimeDuration& aStartTime, + double aPlaybackRate) +{ + Nullable<TimeDuration> result; + + Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime); + if (!timelineTime.IsNull()) { + result.SetValue((timelineTime.Value() - aStartTime) + .MultDouble(aPlaybackRate)); + } + + return result; +} + ////////////////////////// nsTransitionManager //////////////////////////// NS_IMPL_CYCLE_COLLECTION(nsTransitionManager, mEventDispatcher) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTransitionManager, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTransitionManager, Release) void @@ -741,16 +799,39 @@ nsTransitionManager::ConsiderStartingTra i == currentIndex || (animations[i]->GetEffect() && animations[i]->GetEffect()->AsTransition()->TransitionProperty() != aProperty), "duplicate transitions for property"); } #endif if (haveCurrentTransition) { + // If this new transition is replacing an existing transition that is running + // on the compositor, we store select parameters from the replaced transition + // so that later, once all scripts have run, we can update the start value + // of the transition using TimeStamp::Now(). This allows us to avoid a + // large jump when starting a new transition when the main thread lags behind + // the compositor. + if (oldPT->IsCurrent() && + oldPT->IsRunningOnCompositor() && + !oldPT->GetAnimation()->GetStartTime().IsNull() && + timeline == oldPT->GetAnimation()->GetTimeline()) { + const AnimationPropertySegment& segment = + oldPT->Properties()[0].mSegments[0]; + pt->mReplacedTransition.emplace( + ElementPropertyTransition::ReplacedTransitionProperties({ + oldPT->GetAnimation()->GetStartTime().Value(), + oldPT->GetAnimation()->PlaybackRate(), + oldPT->SpecifiedTiming(), + segment.mTimingFunction, + segment.mFromValue, + segment.mToValue + }) + ); + } animations[currentIndex]->CancelFromStyle(); oldPT = nullptr; // Clear pointer so it doesn't dangle animations[currentIndex] = animation; } else { if (!animations.AppendElement(animation)) { NS_WARNING("out of memory"); return; }
--- a/layout/style/nsTransitionManager.h +++ b/layout/style/nsTransitionManager.h @@ -90,16 +90,30 @@ struct ElementPropertyTransition : publi // in again when the transition is back to 2px, the mReversePortion // for the third transition (from 0px/2px to 10px) will be 0.8. double mReversePortion; // Compute the portion of the *value* space that we should be through // at the current time. (The input to the transition timing function // has time units, the output has value units.) double CurrentValuePortion() const; + + // For a new transition interrupting an existing transition on the + // compositor, update the start value to match the value of the replaced + // transitions at the current time. + void UpdateStartValueFromReplacedTransition(); + + struct ReplacedTransitionProperties { + TimeDuration mStartTime; + double mPlaybackRate; + TimingParams mTiming; + Maybe<ComputedTimingFunction> mTimingFunction; + StyleAnimationValue mFromValue, mToValue; + }; + Maybe<ReplacedTransitionProperties> mReplacedTransition; }; namespace dom { class CSSTransition final : public Animation { public: explicit CSSTransition(nsIGlobalObject* aGlobal) @@ -179,16 +193,27 @@ public: void SetOwningElement(const OwningElementRef& aElement) { mOwningElement = aElement; } // True for transitions that are generated from CSS markup and continue to // reflect changes to that markup. bool IsTiedToMarkup() const { return mOwningElement.IsSet(); } + // Return the animation current time based on a given TimeStamp, a given + // start time and a given playbackRate on a given timeline. This is useful + // when we estimate the current animated value running on the compositor + // because the animation on the compositor may be running ahead while + // main-thread is busy. + static Nullable<TimeDuration> GetCurrentTimeAt( + const DocumentTimeline& aTimeline, + const TimeStamp& aBaseTime, + const TimeDuration& aStartTime, + double aPlaybackRate); + protected: virtual ~CSSTransition() { MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared " "before a CSS transition is destroyed"); } // Animation overrides