// https://w3c.github.io/web-animations/#finish-an-animation void Animation::Finish(ErrorResult& aRv) { if (mPlaybackRate == 0 || (mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } AutoMutationBatchForAnimation mb(*this); // Seek to the end TimeDuration limit = mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0); bool didChange = GetCurrentTime() != Nullable<TimeDuration>(limit); SilentlySetCurrentTime(limit); // If we are paused or play-pending we need to fill in the start time in // order to transition to the finished state. // // We only do this, however, if we have an active timeline. If we have an // inactive timeline we can't transition into the finished state just like // we can't transition to the running state (this finished state is really // a substate of the running state). if (mStartTime.IsNull() && mTimeline && !mTimeline->GetCurrentTime().IsNull()) { mStartTime.SetValue(mTimeline->GetCurrentTime().Value() - limit.MultDouble(1.0 / mPlaybackRate)); didChange = true; } // If we just resolved the start time for a pause or play-pending // animation, we need to clear the task. We don't do this as a branch of // the above however since we can have a play-pending animation with a // resolved start time if we aborted a pause operation. if (!mStartTime.IsNull() && (mPendingState == PendingState::PlayPending || mPendingState == PendingState::PausePending)) { if (mPendingState == PendingState::PausePending) { mHoldTime.SetNull(); } CancelPendingTasks(); didChange = true; if (mReady) { mReady->MaybeResolve(this); } } UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync); if (didChange && IsRelevant()) { nsNodeUtils::AnimationChanged(this); } PostUpdate(); }
// http://w3c.github.io/web-animations/#silently-set-the-current-time void Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime) { if (!mHoldTime.IsNull() || mStartTime.IsNull() || !mTimeline || mTimeline->GetCurrentTime().IsNull() || mPlaybackRate == 0.0) { mHoldTime.SetValue(aSeekTime); if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) { mStartTime.SetNull(); } } else { mStartTime.SetValue(mTimeline->GetCurrentTime().Value() - (aSeekTime.MultDouble(1 / mPlaybackRate))); } mPreviousCurrentTime.SetNull(); }
virtual void ScheduleNextTick(TimeStamp aNowTime) { static const TimeDuration kMinSaneInterval = TimeDuration::FromMilliseconds(3); // 330Hz static const TimeDuration kMaxSaneInterval = TimeDuration::FromMilliseconds(44); // 23Hz static const TimeDuration kNegativeMaxSaneInterval = TimeDuration::FromMilliseconds(-44); // Saves conversions for abs interval TimeStamp lastVblank; TimeDuration vblankInterval; if (!mPreferHwTiming || NS_OK != GetVBlankInfo(lastVblank, vblankInterval) || vblankInterval > kMaxSaneInterval || vblankInterval < kMinSaneInterval || (aNowTime - lastVblank) > kMaxSaneInterval || (aNowTime - lastVblank) < kNegativeMaxSaneInterval) { // Use the default timing without vsync PreciseRefreshDriverTimer::ScheduleNextTick(aNowTime); return; } TimeStamp newTarget = lastVblank + vblankInterval; // Base target // However, timer callback might return early (or late, but that wouldn't bother us), and vblankInterval // appears to be slightly (~1%) different on each call (probably the OS measuring recent actual interval[s]) // and since we don't want to re-target the same vsync, we keep advancing in vblank intervals until we find the // next safe target (next vsync, but not within 10% interval of previous target). // This is typically 0 or 1 iteration: // If we're too early, next vsync would be the one we've already targeted (1 iteration). // If the timer returned late, no iteration will be required. const double kSameVsyncThreshold = 0.1; while (newTarget <= mTargetTime + vblankInterval.MultDouble(kSameVsyncThreshold)) { newTarget += vblankInterval; } // To make sure we always hit the same "side" of the signal: // round the delay up (by adding 1, since we later floor) and add a little (10% by default). // Note that newTarget doesn't change (and is the next vblank) as a reference when we're back. static const double kDefaultPhaseShiftPercent = 10; static const double phaseShiftFactor = 0.01 * (Preferences::GetInt("layout.frame_rate.vsync.phasePercentage", kDefaultPhaseShiftPercent) % 100); double phaseDelay = 1.0 + vblankInterval.ToMilliseconds() * phaseShiftFactor; // ms until the next time we should tick double delayMs = (newTarget - aNowTime).ToMilliseconds() + phaseDelay; // Make sure the delay is never negative. uint32_t delay = static_cast<uint32_t>(delayMs < 0 ? 0 : delayMs); // log info & lateness LOG("[%p] precise dwm-vsync timer last tick late by %f ms, next tick in %d ms", this, (aNowTime - mTargetTime).ToMilliseconds(), delay); // then schedule the timer LOG("[%p] scheduling callback for %d ms (2)", this, delay); mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT); mTargetTime = newTarget; }