Changeset 238128 in webkit


Ignore:
Timestamp:
Nov 13, 2018 2:22:56 AM (5 years ago)
Author:
graouts@webkit.org
Message:

[Web Animations] Don't schedule animation frames or update style while an accelerated animation is running
https://bugs.webkit.org/show_bug.cgi?id=191542
<rdar://problem/45356027>

Reviewed by Simon Fraser.

Source/WebCore:

Test: animations/no-style-recalc-during-accelerated-animation.html

In order to be more power-efficient, we stop scheduling calls to updateAnimationsAndSendEvents() when running only accelerated
animations. To do that, we prevent scheduling further animation resolution if we're in the process of updating animations, and
when we are done, call the new DocumentTimeline::scheduleNextTick() method that will check whether we have only accelerated
animations running, and in that case check which of those animations needs an update the soonest and starts a timer scheduled
for that time when we'll schedule animation resolution.

By default, animations compute the time until their natural completion but in the case of CSS Animations, we want to make sure
we also update animations in-flight to dispatch "animationiteration" events.

  • animation/AnimationEffect.h: Make the simpleIterationProgress() public so it can be called by WebAnimation::timeToNextTick().
  • animation/DocumentTimeline.cpp:

(WebCore::DocumentTimeline::DocumentTimeline): Create the m_tickScheduleTimer and set it up to call scheduleAnimationResolutionIfNeeded().
(WebCore::DocumentTimeline::suspendAnimations): If we don't already have a cached current time, cache the current time.
(WebCore::DocumentTimeline::resumeAnimations): Reset the cached current time to ensure we'll get a fresh one when updating animations next.
(WebCore::DocumentTimeline::liveCurrentTime const): Factor the code to compute the current time out of currentTime() so that we can
cache the current time in suspendAnimations() without also automatically clearing the current time.
(WebCore::DocumentTimeline::currentTime): Use liveCurrentTime() and cacheCurrentTime() since much of the code from this function has been
factored out into those. Additionally, we were failing to clear the current time if called inside an animation frame, which we now do correctly
by virtue of using cacheCurrentTime(). This fixes some flakiness.
(WebCore::DocumentTimeline::cacheCurrentTime): Factor the code to cache the current time out of currentTime().
(WebCore::DocumentTimeline::maybeClearCachedCurrentTime): No need to clear the current time if we get suspended.
(WebCore::DocumentTimeline::scheduleAnimationResolutionIfNeeded): Prevent scheduling an animation update if we're in the middle of one already,
scheduleNextTick() will be called after animations are updated to see if we should schedule an animation update instead.
(WebCore::DocumentTimeline::unscheduleAnimationResolution): Cancel the m_tickScheduleTimer if we need to unschedule animation resolution.
(WebCore::DocumentTimeline::animationResolutionTimerFired): Factor the call to applyPendingAcceleratedAnimations() out of updateAnimationsAndSendEvents()
and call scheduleNextTick().
(WebCore::DocumentTimeline::updateAnimationsAndSendEvents): Set the new m_isUpdatingAnimations member variable to true while this function is running.
(WebCore::DocumentTimeline::scheduleNextTick): Schedule an animation update immediately if we have any relevant animation that is not accelerated.
Otherwise, iterate through all animations to figure out the earliest moment at which we need to update animations.
(WebCore::DocumentTimeline::updateListOfElementsWithRunningAcceleratedAnimationsForElement): Use the new WebAnimation::isRunningAccelerated() function.

  • animation/DocumentTimeline.h:
  • animation/WebAnimation.cpp:

(WebCore::WebAnimation::isRunningAccelerated const): Since we end up checking if an animation is running with an accelerated effect, we introduce a new
function to get that information directly through the WebAnimation object without bothering about its effect.
(WebCore::WebAnimation::resolve): We should only call updateFinishedState() here since timingDidChange() would also notify the timeline about a potential
change in relevance, which is not necessary and which would schedule an animation frame even for animations that are accelerated.
(WebCore::WebAnimation::timeToNextTick const): Compute the time until our animation completion or, in the case of CSS animations, the next iteration.

  • animation/WebAnimation.h:

LayoutTests:

Add a test that checks that we make only minimal style updates and still dispatch events while an accelerated animation is running.

  • animations/no-style-recalc-during-accelerated-animation-expected.txt: Added.
  • animations/no-style-recalc-during-accelerated-animation.html: Added.
  • fast/layers/no-clipping-overflow-hidden-added-after-transform-expected.html:
  • fast/layers/no-clipping-overflow-hidden-added-after-transform.html: Change the colors to avoid a tiny ImageOnlyFailure.
  • platform/win/TestExpectations: Mark some regressions tracked by webkit.org/b/191584.
Location:
trunk
Files:
2 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r238125 r238128  
     12018-11-12  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Don't schedule animation frames or update style while an accelerated animation is running
     4        https://bugs.webkit.org/show_bug.cgi?id=191542
     5        <rdar://problem/45356027>
     6
     7        Reviewed by Simon Fraser.
     8
     9        Add a test that checks that we make only minimal style updates and still dispatch events while an accelerated animation is running.
     10
     11        * animations/no-style-recalc-during-accelerated-animation-expected.txt: Added.
     12        * animations/no-style-recalc-during-accelerated-animation.html: Added.
     13        * fast/layers/no-clipping-overflow-hidden-added-after-transform-expected.html:
     14        * fast/layers/no-clipping-overflow-hidden-added-after-transform.html: Change the colors to avoid a tiny ImageOnlyFailure.
     15        * platform/win/TestExpectations: Mark some regressions tracked by webkit.org/b/191584.
     16
    1172018-11-12  Darshan Kadu  <darsh7807@gmail.com>
    218
  • trunk/LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform-expected.html

    r154281 r238128  
    1010#overflowHidden {
    1111    overflow: hidden;
    12     background: purple;
     12    background: white;
    1313}
    1414
    1515#transformed {
    1616    -webkit-transform: rotate(45deg) translate3d(0, 0, 0);
    17     background: green;
     17    background: black;
    1818}
    1919</style>
  • trunk/LayoutTests/fast/layers/no-clipping-overflow-hidden-added-after-transform.html

    r237587 r238128  
    1010#overflowHidden {
    1111    overflow: hidden;
    12     background: purple;
     12    background: white;
    1313}
    1414
     
    1616    -webkit-transform: rotate(0deg) translate3d(0, 0, 0);
    1717    -webkit-transition: -webkit-transform linear 1ms;
    18     background: green;
     18    background: black;
    1919}
    2020
     
    3333    function transitionFinished() {
    3434        if (window.testRunner)
    35             requestAnimationFrame(() => window.testRunner.notifyDone());
     35            window.testRunner.notifyDone();
    3636    }
    3737
  • trunk/LayoutTests/platform/win/TestExpectations

    r238071 r238128  
    42524252webkit.org/b/191368 imported/blink/fast/text/international/complex-text-trailing-space.html [ Crash ]
    42534253webkit.org/b/191368 imported/blink/fast/text/sub-pixel/complex-text-preferred-width.html [ Crash ]
     4254
     4255webkit.org/b/191584 animations/animation-direction-normal.html [ Failure ]
     4256webkit.org/b/191584 animations/animation-direction-reverse.html [ Failure ]
     4257webkit.org/b/191584 animations/dynamic-stylesheet-loading.html [ Failure ]
     4258webkit.org/b/191584 animations/play-state-paused.html [ Failure ]
     4259webkit.org/b/191584 animations/transform-non-accelerated.html [ Failure ]
     4260webkit.org/b/191584 transitions/start-transform-transition.html [ Failure ]
     4261webkit.org/b/191584 http/wpt/css/css-animations/set-animation-play-state-to-paused-001.html [ ImageOnlyFailure ]
     4262webkit.org/b/191584 webanimations/accelerated-animation-with-delay.html [ ImageOnlyFailure ]
     4263webkit.org/b/191584 webanimations/accelerated-transition-by-removing-property.html [ ImageOnlyFailure ]
     4264webkit.org/b/191584 fast/animation/css-animation-resuming-when-visible-with-style-change.html [ Timeout ]
  • trunk/Source/WebCore/ChangeLog

    r238126 r238128  
     12018-11-12  Antoine Quint  <graouts@apple.com>
     2
     3        [Web Animations] Don't schedule animation frames or update style while an accelerated animation is running
     4        https://bugs.webkit.org/show_bug.cgi?id=191542
     5        <rdar://problem/45356027>
     6
     7        Reviewed by Simon Fraser.
     8
     9        Test: animations/no-style-recalc-during-accelerated-animation.html
     10
     11        In order to be more power-efficient, we stop scheduling calls to updateAnimationsAndSendEvents() when running only accelerated
     12        animations. To do that, we prevent scheduling further animation resolution if we're in the process of updating animations, and
     13        when we are done, call the new DocumentTimeline::scheduleNextTick() method that will check whether we have only accelerated
     14        animations running, and in that case check which of those animations needs an update the soonest and starts a timer scheduled
     15        for that time when we'll schedule animation resolution.
     16
     17        By default, animations compute the time until their natural completion but in the case of CSS Animations, we want to make sure
     18        we also update animations in-flight to dispatch "animationiteration" events.
     19
     20        * animation/AnimationEffect.h: Make the simpleIterationProgress() public so it can be called by WebAnimation::timeToNextTick().
     21        * animation/DocumentTimeline.cpp:
     22        (WebCore::DocumentTimeline::DocumentTimeline): Create the m_tickScheduleTimer and set it up to call scheduleAnimationResolutionIfNeeded().
     23        (WebCore::DocumentTimeline::suspendAnimations): If we don't already have a cached current time, cache the current time.
     24        (WebCore::DocumentTimeline::resumeAnimations): Reset the cached current time to ensure we'll get a fresh one when updating animations next.
     25        (WebCore::DocumentTimeline::liveCurrentTime const): Factor the code to compute the current time out of currentTime() so that we can
     26        cache the current time in suspendAnimations() without also automatically clearing the current time.
     27        (WebCore::DocumentTimeline::currentTime): Use liveCurrentTime() and cacheCurrentTime() since much of the code from this function has been
     28        factored out into those. Additionally, we were failing to clear the current time if called inside an animation frame, which we now do correctly
     29        by virtue of using cacheCurrentTime(). This fixes some flakiness.
     30        (WebCore::DocumentTimeline::cacheCurrentTime): Factor the code to cache the current time out of currentTime().
     31        (WebCore::DocumentTimeline::maybeClearCachedCurrentTime): No need to clear the current time if we get suspended.
     32        (WebCore::DocumentTimeline::scheduleAnimationResolutionIfNeeded): Prevent scheduling an animation update if we're in the middle of one already,
     33        scheduleNextTick() will be called after animations are updated to see if we should schedule an animation update instead.
     34        (WebCore::DocumentTimeline::unscheduleAnimationResolution): Cancel the m_tickScheduleTimer if we need to unschedule animation resolution.
     35        (WebCore::DocumentTimeline::animationResolutionTimerFired): Factor the call to applyPendingAcceleratedAnimations() out of updateAnimationsAndSendEvents()
     36        and call scheduleNextTick().
     37        (WebCore::DocumentTimeline::updateAnimationsAndSendEvents): Set the new m_isUpdatingAnimations member variable to true while this function is running.
     38        (WebCore::DocumentTimeline::scheduleNextTick): Schedule an animation update immediately if we have any relevant animation that is not accelerated.
     39        Otherwise, iterate through all animations to figure out the earliest moment at which we need to update animations.
     40        (WebCore::DocumentTimeline::updateListOfElementsWithRunningAcceleratedAnimationsForElement): Use the new WebAnimation::isRunningAccelerated() function.
     41        * animation/DocumentTimeline.h:
     42        * animation/WebAnimation.cpp:
     43        (WebCore::WebAnimation::isRunningAccelerated const): Since we end up checking if an animation is running with an accelerated effect, we introduce a new
     44        function to get that information directly through the WebAnimation object without bothering about its effect.
     45        (WebCore::WebAnimation::resolve): We should only call updateFinishedState() here since timingDidChange() would also notify the timeline about a potential
     46        change in relevance, which is not necessary and which would schedule an animation frame even for animations that are accelerated.
     47        (WebCore::WebAnimation::timeToNextTick const): Compute the time until our animation completion or, in the case of CSS animations, the next iteration.
     48        * animation/WebAnimation.h:
     49
    1502018-11-13  Miguel Gomez  <magomez@igalia.com>
    251
  • trunk/Source/WebCore/animation/AnimationEffect.h

    r237853 r238128  
    9191    std::optional<Seconds> activeTime() const;
    9292    Seconds endTime() const;
     93    std::optional<double> simpleIterationProgress() const;
    9394    std::optional<double> iterationProgress() const;
    9495    std::optional<double> currentIteration() const;
     
    105106
    106107    std::optional<double> overallProgress() const;
    107     std::optional<double> simpleIterationProgress() const;
    108108    AnimationEffect::ComputedDirection currentDirection() const;
    109109    std::optional<double> directedProgress() const;
  • trunk/Source/WebCore/animation/DocumentTimeline.cpp

    r237868 r238128  
    6464    , m_document(&document)
    6565    , m_originTime(originTime)
     66    , m_tickScheduleTimer(*this, &DocumentTimeline::scheduleAnimationResolutionIfNeeded)
    6667#if !USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
    6768    , m_animationResolutionTimer(*this, &DocumentTimeline::animationResolutionTimerFired)
     
    209210        return;
    210211
     212    if (!m_cachedCurrentTime)
     213        m_cachedCurrentTime = liveCurrentTime();
     214
    211215    for (const auto& animation : m_animations)
    212216        animation->setSuspended(true);
     
    223227    if (!animationsAreSuspended())
    224228        return;
     229
     230    m_cachedCurrentTime = std::nullopt;
    225231
    226232    m_isSuspended = false;
     
    245251    }
    246252    return count;
     253}
     254
     255Seconds DocumentTimeline::liveCurrentTime() const
     256{
     257#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
     258    return m_document->animationScheduler().lastTimestamp();
     259#else
     260    return Seconds(m_document->domWindow()->nowTimestamp());
     261#endif
    247262}
    248263
     
    260275    }
    261276
     277    auto currentTime = liveCurrentTime();
     278
    262279#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
    263280    // If we're in the middle of firing a frame, either due to a requestAnimationFrame callback
     
    265282    // the timestamp for requestAnimationFrame() callbacks.
    266283    if (m_document->animationScheduler().isFiring())
    267         m_cachedCurrentTime = m_document->animationScheduler().lastTimestamp();
     284        cacheCurrentTime(currentTime);
    268285#endif
    269286
     
    274291        // like setTimeout() or handling events will get a time that's only updating at around 60fps, or less if
    275292        // we're throttled.
    276         auto lastAnimationSchedulerTimestamp = m_document->animationScheduler().lastTimestamp();
     293        auto lastAnimationSchedulerTimestamp = currentTime;
    277294        auto delta = Seconds(m_document->domWindow()->nowTimestamp()) - lastAnimationSchedulerTimestamp;
    278295        int frames = std::floor(delta.seconds() / animationInterval().seconds());
    279         m_cachedCurrentTime = lastAnimationSchedulerTimestamp + Seconds(frames * animationInterval().seconds());
     296        cacheCurrentTime(lastAnimationSchedulerTimestamp + Seconds(frames * animationInterval().seconds()));
    280297#else
    281         m_cachedCurrentTime = Seconds(m_document->domWindow()->nowTimestamp());
     298        cacheCurrentTime(currentTime);
    282299#endif
    283         // We want to be sure to keep this time cached until we've both finished running JS and finished updating
    284         // animations, so we schedule the invalidation task and register a whenIdle callback on the VM, which will
    285         // fire syncronously if no JS is running.
    286         m_waitingOnVMIdle = true;
    287         if (!m_currentTimeClearingTaskQueue.hasPendingTasks())
    288             m_currentTimeClearingTaskQueue.enqueueTask(std::bind(&DocumentTimeline::maybeClearCachedCurrentTime, this));
    289         m_document->vm().whenIdle([this, protectedThis = makeRefPtr(this)]() {
    290             m_waitingOnVMIdle = false;
    291             maybeClearCachedCurrentTime();
    292         });
    293300    }
    294301    return m_cachedCurrentTime.value() - m_originTime;
     302}
     303
     304void DocumentTimeline::cacheCurrentTime(Seconds newCurrentTime)
     305{
     306    m_cachedCurrentTime = newCurrentTime;
     307    // We want to be sure to keep this time cached until we've both finished running JS and finished updating
     308    // animations, so we schedule the invalidation task and register a whenIdle callback on the VM, which will
     309    // fire syncronously if no JS is running.
     310    m_waitingOnVMIdle = true;
     311    if (!m_currentTimeClearingTaskQueue.hasPendingTasks())
     312        m_currentTimeClearingTaskQueue.enqueueTask(std::bind(&DocumentTimeline::maybeClearCachedCurrentTime, this));
     313    m_document->vm().whenIdle([this, protectedThis = makeRefPtr(this)]() {
     314        m_waitingOnVMIdle = false;
     315        maybeClearCachedCurrentTime();
     316    });
    295317}
    296318
     
    301323    // we're guaranteed to have a consistent current time reported for all work happening in a given
    302324    // JS frame or throughout updating animations in WebCore.
    303     if (!m_waitingOnVMIdle && !m_currentTimeClearingTaskQueue.hasPendingTasks())
     325    if (!m_isSuspended && !m_waitingOnVMIdle && !m_currentTimeClearingTaskQueue.hasPendingTasks())
    304326        m_cachedCurrentTime = std::nullopt;
    305327}
     
    307329void DocumentTimeline::scheduleAnimationResolutionIfNeeded()
    308330{
    309     if (!m_isSuspended && !m_animations.isEmpty())
     331    if (!m_isUpdatingAnimations && !m_isSuspended && !m_animations.isEmpty())
    310332        scheduleAnimationResolution();
    311333}
     
    338360void DocumentTimeline::unscheduleAnimationResolution()
    339361{
     362    m_tickScheduleTimer.stop();
    340363#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
    341364    m_document->animationScheduler().unscheduleWebAnimationsResolution();
     
    354377{
    355378    updateAnimationsAndSendEvents();
     379    applyPendingAcceleratedAnimations();
     380    scheduleNextTick();
    356381}
    357382
     
    359384{
    360385    m_numberOfAnimationTimelineInvalidationsForTesting++;
     386
     387    m_isUpdatingAnimations = true;
    361388
    362389    // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
     
    423450        transitionDidComplete(completedTransition);
    424451
    425     applyPendingAcceleratedAnimations();
     452    m_isUpdatingAnimations = false;
    426453}
    427454
     
    437464        }
    438465    }
     466}
     467
     468void DocumentTimeline::scheduleNextTick()
     469{
     470    // There is no tick to schedule if we don't have any relevant animations.
     471    if (m_animations.isEmpty())
     472        return;
     473
     474    for (const auto& animation : m_animations) {
     475        if (!animation->isRunningAccelerated()) {
     476            scheduleAnimationResolutionIfNeeded();
     477            return;
     478        }
     479    }
     480
     481    Seconds scheduleDelay = Seconds::infinity();
     482
     483    for (const auto& animation : m_animations) {
     484        auto animationTimeToNextRequiredTick = animation->timeToNextTick();
     485        if (animationTimeToNextRequiredTick < animationInterval()) {
     486            scheduleAnimationResolutionIfNeeded();
     487            return;
     488        }
     489        scheduleDelay = std::min(scheduleDelay, animationTimeToNextRequiredTick);
     490    }
     491
     492    if (scheduleDelay < Seconds::infinity())
     493        m_tickScheduleTimer.startOneShot(scheduleDelay);
    439494}
    440495
     
    541596    bool runningAnimationsForElementAreAllAccelerated = !animations.isEmpty();
    542597    for (const auto& animation : animations) {
    543         if (is<KeyframeEffect>(animation->effect()) && !downcast<KeyframeEffect>(animation->effect())->isRunningAccelerated()) {
     598        if (!animation->isRunningAccelerated()) {
    544599            runningAnimationsForElementAreAllAccelerated = false;
    545600            break;
  • trunk/Source/WebCore/animation/DocumentTimeline.h

    r237726 r238128  
    8686    DocumentTimeline(Document&, Seconds);
    8787
     88    Seconds liveCurrentTime() const;
     89    void cacheCurrentTime(Seconds);
    8890    void scheduleAnimationResolutionIfNeeded();
    8991    void scheduleInvalidationTaskIfNeeded();
     
    9799    void updateListOfElementsWithRunningAcceleratedAnimationsForElement(Element&);
    98100    void transitionDidComplete(RefPtr<CSSTransition>);
     101    void scheduleNextTick();
    99102
    100103    RefPtr<Document> m_document;
     
    102105    bool m_isSuspended { false };
    103106    bool m_waitingOnVMIdle { false };
     107    bool m_isUpdatingAnimations { false };
    104108    std::optional<Seconds> m_cachedCurrentTime;
    105109    GenericTaskQueue<Timer> m_currentTimeClearingTaskQueue;
     
    108112    unsigned m_numberOfAnimationTimelineInvalidationsForTesting { 0 };
    109113    HashSet<Element*> m_elementsWithRunningAcceleratedAnimations;
     114    Timer m_tickScheduleTimer;
    110115
    111116#if !USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
  • trunk/Source/WebCore/animation/WebAnimation.cpp

    r237868 r238128  
    10951095}
    10961096
     1097bool WebAnimation::isRunningAccelerated() const
     1098{
     1099    return is<KeyframeEffect>(m_effect) && downcast<KeyframeEffect>(*m_effect).isRunningAccelerated();
     1100}
     1101
    10971102bool WebAnimation::needsTick() const
    10981103{
     
    11151120void WebAnimation::resolve(RenderStyle& targetStyle)
    11161121{
    1117     timingDidChange(DidSeek::No, SynchronouslyNotify::Yes);
     1122    updateFinishedState(DidSeek::No, SynchronouslyNotify::Yes);
    11181123    if (m_effect)
    11191124        m_effect->apply(targetStyle);
     
    11951200}
    11961201
     1202Seconds WebAnimation::timeToNextTick() const
     1203{
     1204    ASSERT(isRunningAccelerated());
     1205
     1206    if (pending())
     1207        return 0_s;
     1208
     1209    // If we're not running, there's no telling when we'll end.
     1210    if (playState() != PlayState::Running)
     1211        return Seconds::infinity();
     1212
     1213    // CSS Animations dispatch events for each iteration, so compute the time until
     1214    // the end of this iteration. Any other animation only cares about remaning total time.
     1215    if (isCSSAnimation()) {
     1216        // If we're actively running, we need the time until the next iteration.
     1217        if (auto iterationProgress = effect()->simpleIterationProgress())
     1218            return effect()->iterationDuration() * (1 - iterationProgress.value());
     1219
     1220        // Otherwise we're probably in the before phase waiting to reach our start time.
     1221        if (auto animationCurrentTime = currentTime()) {
     1222            // If our current time is negative, we need to be scheduled to be resolved at the inverse
     1223            // of our current time, unless we fill backwards, in which case we want to invalidate as
     1224            // soon as possible.
     1225            auto localTime = animationCurrentTime.value();
     1226            if (localTime < 0_s)
     1227                return -localTime;
     1228            if (localTime < effect()->delay())
     1229                return effect()->delay() - localTime;
     1230        }
     1231    } else if (auto animationCurrentTime = currentTime())
     1232        return effect()->endTime() - animationCurrentTime.value();
     1233
     1234    ASSERT_NOT_REACHED();
     1235    return Seconds::infinity();
     1236}
     1237
    11971238} // namespace WebCore
  • trunk/Source/WebCore/animation/WebAnimation.h

    r237854 r238128  
    105105    virtual bool needsTick() const;
    106106    virtual void tick();
     107    Seconds timeToNextTick() const;
    107108    virtual void resolve(RenderStyle&);
    108109    void effectTargetDidChange(Element* previousTarget, Element* newTarget);
     
    110111    void applyPendingAcceleratedActions();
    111112
     113    bool isRunningAccelerated() const;
    112114    bool isRelevant() const { return m_isRelevant; }
    113115    void effectTimingDidChange();
Note: See TracChangeset for help on using the changeset viewer.