| Differences between
and this patch
- a/Source/WebCore/ChangeLog +61 lines
Lines 1-3 a/Source/WebCore/ChangeLog_sec1
1
2019-11-30  Antoine Quint  <graouts@apple.com>
2
3
        [Web Animations] Enable WebAnimationsCSSIntegrationEnabled by default
4
        https://bugs.webkit.org/show_bug.cgi?id=204650
5
        <rdar://problem/57534005>
6
7
        Reviewed by NOBODY (OOPS!).
8
9
        Tests: webanimations/no-scheduling-while-filling-accelerated.html
10
               webanimations/no-scheduling-while-filling-non-accelerated.html
11
12
        For two different reasons, we would continuously schedule updates for animations in their "after" phase, ie. when they would have "fill: forwards"
13
        and be finished.
14
15
        In the case of non-accelerated animations, that was because we would also schedule an immedate update in DocumentTimeline::scheduleNextTick() provided
16
        we had relevant animations that weren't accelerated. But since animations in their "after" phase are still considered relevant, which means that they
17
        would override the base style with an animated value, this caused the unnecessary scheduled updates.
18
19
        To address this, we run WebAnimation::timeToNextTick() in DocumentTimeline::scheduleNextTick() for all relevant animations and we teach that function
20
        to schedule an immediate update only during the "active" phase, and to schedule a timed update only in the "before" phase computing the delay until
21
        the animation enters the "active" phase.
22
23
        While performing this work, we found a couple of other issues that weren't apparent until we had this more efficient scheduling in place.
24
25
        First, we would not allow any additional calls to DocumentTimeline::scheduleAnimationResolution() to actually schedule an update while we were in the
26
        process of updating animations. While this wasn't a problem before because the mere presence of relevant animations would cause an upadte, we now had
27
        to allow any animation changing timing properties as promises would resolve and events would be dispatched during the animation update to cause further
28
        animation updates to be scheduled. So we moved the "m_animationResolutionScheduled" flag reset earlier in DocumentTimeline::updateAnimationsAndSendEvents()
29
        before we actually run the animation update procedure.
30
31
        Following that change, we also had to make sure that timing changes made through the evaluation of the pending play and pause tasks would _not_ cause
32
        animations to be scheduled, which meant that an animation that became non-pending (ie. its first tick occured) would always schedule one immediate
33
        animation update. So now we have an extra "silent" flag to WebAnimation::timingDidChange() which is only set to true when called from either
34
        WebAnimation::runPendingPlayTask() or WebAnimation::runPendingPauseTask().
35
36
        Finally, one existing test showed that calling KeyframeEffect::setKeyframes() while running animations didn't cause an animation update to be scheduled
37
        since a call to WebAnimation::effectTimingDidChange() was lacking. This is now addressed.
38
39
        As for accelerated animations, the extraneous animation scheduling occured because we would get in a state where we would record an "accelerated action"
40
        to stop the accelerated animation but the accelerated animation had already completed and the renderer for the target element was no longer composited.
41
        This meant that KeyframeEffect::applyPendingAcceleratedActions() would not perform any work and the pending accelerated action would remain in the
42
        DocumentTimeline queue and cause an update to be scheduled to try again, endlessly.
43
44
        To address that, we first check in KeyframeEffect::addPendingAcceleratedAction() if the recorded action differs from the last recorded action, avoiding
45
        unnecessary work, and in KeyframeEffect::applyPendingAcceleratedActions(), if our last recorded action was "Stop" and the renderer was not composited,
46
        we discard all pending accelerated actions.
47
48
        * animation/DocumentTimeline.cpp:
49
        (WebCore::DocumentTimeline::updateAnimationsAndSendEvents):
50
        (WebCore::DocumentTimeline::scheduleNextTick):
51
        * animation/KeyframeEffect.cpp:
52
        (WebCore::KeyframeEffect::setKeyframes):
53
        (WebCore::KeyframeEffect::addPendingAcceleratedAction):
54
        (WebCore::KeyframeEffect::applyPendingAcceleratedActions):
55
        * animation/WebAnimation.cpp:
56
        (WebCore::WebAnimation::timingDidChange):
57
        (WebCore::WebAnimation::runPendingPlayTask):
58
        (WebCore::WebAnimation::runPendingPauseTask):
59
        (WebCore::WebAnimation::timeToNextTick const):
60
        * animation/WebAnimation.h:
61
1
2019-11-30  Philippe Normand  <pnormand@igalia.com>
62
2019-11-30  Philippe Normand  <pnormand@igalia.com>
2
63
3
        Unreviewed, follow-up to r252938, now fixing the MSE/EME build
64
        Unreviewed, follow-up to r252938, now fixing the MSE/EME build
- a/Source/WebCore/animation/DocumentTimeline.cpp -9 / +6 lines
Lines 350-360 void DocumentTimeline::updateAnimationsAndSendEvents(DOMHighResTimeStamp timesta a/Source/WebCore/animation/DocumentTimeline.cpp_sec1
350
    if (m_isSuspended || !m_animationResolutionScheduled || !shouldRunUpdateAnimationsAndSendEventsIgnoringSuspensionState())
350
    if (m_isSuspended || !m_animationResolutionScheduled || !shouldRunUpdateAnimationsAndSendEventsIgnoringSuspensionState())
351
        return;
351
        return;
352
352
353
    // Updating animations and sending events may invalidate the timing of some animations, so we must set the m_animationResolutionScheduled
354
    // flag to false prior to running that procedure to allow animation with timing model updates to schedule updates.
355
    m_animationResolutionScheduled = false;
356
353
    internalUpdateAnimationsAndSendEvents();
357
    internalUpdateAnimationsAndSendEvents();
354
    applyPendingAcceleratedAnimations();
358
    applyPendingAcceleratedAnimations();
355
359
356
    m_animationResolutionScheduled = false;
360
    if (!m_animationResolutionScheduled)
357
    scheduleNextTick();
361
        scheduleNextTick();
358
}
362
}
359
363
360
void DocumentTimeline::internalUpdateAnimationsAndSendEvents()
364
void DocumentTimeline::internalUpdateAnimationsAndSendEvents()
Lines 529-541 void DocumentTimeline::scheduleNextTick() a/Source/WebCore/animation/DocumentTimeline.cpp_sec2
529
    if (m_animations.isEmpty())
533
    if (m_animations.isEmpty())
530
        return;
534
        return;
531
535
532
    for (const auto& animation : m_animations) {
533
        if (!animation->isRunningAccelerated()) {
534
            scheduleAnimationResolution();
535
            return;
536
        }
537
    }
538
539
    Seconds scheduleDelay = Seconds::infinity();
536
    Seconds scheduleDelay = Seconds::infinity();
540
537
541
    for (const auto& animation : m_animations) {
538
    for (const auto& animation : m_animations) {
- a/Source/WebCore/animation/KeyframeEffect.cpp -2 / +13 lines
Lines 663-669 Vector<Strong<JSObject>> KeyframeEffect::getKeyframes(JSGlobalObject& lexicalGlo a/Source/WebCore/animation/KeyframeEffect.cpp_sec1
663
663
664
ExceptionOr<void> KeyframeEffect::setKeyframes(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput)
664
ExceptionOr<void> KeyframeEffect::setKeyframes(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput)
665
{
665
{
666
    return processKeyframes(lexicalGlobalObject, WTFMove(keyframesInput));
666
    auto processKeyframesResult = processKeyframes(lexicalGlobalObject, WTFMove(keyframesInput));
667
    if (!processKeyframesResult.hasException() && animation())
668
        animation()->effectTimingDidChange();
669
    return processKeyframesResult;
667
}
670
}
668
671
669
ExceptionOr<void> KeyframeEffect::processKeyframes(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput)
672
ExceptionOr<void> KeyframeEffect::processKeyframes(JSGlobalObject& lexicalGlobalObject, Strong<JSObject>&& keyframesInput)
Lines 1342-1347 void KeyframeEffect::updateAcceleratedAnimationState() a/Source/WebCore/animation/KeyframeEffect.cpp_sec2
1342
1345
1343
void KeyframeEffect::addPendingAcceleratedAction(AcceleratedAction action)
1346
void KeyframeEffect::addPendingAcceleratedAction(AcceleratedAction action)
1344
{
1347
{
1348
    if (action == m_lastRecordedAcceleratedAction)
1349
        return;
1350
1345
    if (action == AcceleratedAction::Stop)
1351
    if (action == AcceleratedAction::Stop)
1346
        m_pendingAcceleratedActions.clear();
1352
        m_pendingAcceleratedActions.clear();
1347
    m_pendingAcceleratedActions.append(action);
1353
    m_pendingAcceleratedActions.append(action);
Lines 1381-1388 void KeyframeEffect::applyPendingAcceleratedActions() a/Source/WebCore/animation/KeyframeEffect.cpp_sec3
1381
        return;
1387
        return;
1382
1388
1383
    auto* renderer = this->renderer();
1389
    auto* renderer = this->renderer();
1384
    if (!renderer || !renderer->isComposited())
1390
    if (!renderer || !renderer->isComposited()) {
1391
        // The renderer may no longer be composited because the accelerated animation ended before we had a chance to update it,
1392
        // in which case if we asked for the animation to stop, we can discard the current set of accelerated actions.
1393
        if (m_lastRecordedAcceleratedAction == AcceleratedAction::Stop)
1394
            m_pendingAcceleratedActions.clear();
1385
        return;
1395
        return;
1396
    }
1386
1397
1387
    auto pendingAcceleratedActions = m_pendingAcceleratedActions;
1398
    auto pendingAcceleratedActions = m_pendingAcceleratedActions;
1388
    m_pendingAcceleratedActions.clear();
1399
    m_pendingAcceleratedActions.clear();
- a/Source/WebCore/animation/WebAnimation.cpp -29 / +31 lines
Lines 717-723 ExceptionOr<void> WebAnimation::finish() a/Source/WebCore/animation/WebAnimation.cpp_sec1
717
    return { };
717
    return { };
718
}
718
}
719
719
720
void WebAnimation::timingDidChange(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify)
720
void WebAnimation::timingDidChange(DidSeek didSeek, SynchronouslyNotify synchronouslyNotify, Silently silently)
721
{
721
{
722
    m_shouldSkipUpdatingFinishedStateWhenResolving = false;
722
    m_shouldSkipUpdatingFinishedStateWhenResolving = false;
723
    updateFinishedState(didSeek, synchronouslyNotify);
723
    updateFinishedState(didSeek, synchronouslyNotify);
Lines 727-733 void WebAnimation::timingDidChange(DidSeek didSeek, SynchronouslyNotify synchron a/Source/WebCore/animation/WebAnimation.cpp_sec2
727
        downcast<KeyframeEffect>(*m_effect).animationTimingDidChange();
727
        downcast<KeyframeEffect>(*m_effect).animationTimingDidChange();
728
    }
728
    }
729
729
730
    if (m_timeline)
730
    if (silently == Silently::No && m_timeline)
731
        m_timeline->animationTimingDidChange(*this);
731
        m_timeline->animationTimingDidChange(*this);
732
};
732
};
733
733
Lines 979-985 void WebAnimation::runPendingPlayTask() a/Source/WebCore/animation/WebAnimation.cpp_sec3
979
        m_readyPromise->resolve(*this);
979
        m_readyPromise->resolve(*this);
980
980
981
    // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
981
    // 5. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the synchronously notify flag set to false.
982
    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
982
    timingDidChange(DidSeek::No, SynchronouslyNotify::No, Silently::Yes);
983
983
984
    invalidateEffect();
984
    invalidateEffect();
985
}
985
}
Lines 1105-1111 void WebAnimation::runPendingPauseTask() a/Source/WebCore/animation/WebAnimation.cpp_sec4
1105
1105
1106
    // 6. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the
1106
    // 6. Run the procedure to update an animation's finished state for animation with the did seek flag set to false, and the
1107
    //    synchronously notify flag set to false.
1107
    //    synchronously notify flag set to false.
1108
    timingDidChange(DidSeek::No, SynchronouslyNotify::No);
1108
    timingDidChange(DidSeek::No, SynchronouslyNotify::No, Silently::Yes);
1109
1109
1110
    invalidateEffect();
1110
    invalidateEffect();
1111
}
1111
}
Lines 1289-1325 ExceptionOr<void> WebAnimation::commitStyles() a/Source/WebCore/animation/WebAnimation.cpp_sec5
1289
1289
1290
Seconds WebAnimation::timeToNextTick() const
1290
Seconds WebAnimation::timeToNextTick() const
1291
{
1291
{
1292
    ASSERT(isRunningAccelerated());
1292
    // Any animation that is pending needs immediate resolution.
1293
1294
    if (pending())
1293
    if (pending())
1295
        return 0_s;
1294
        return 0_s;
1296
1295
1297
    // If we're not running, there's no telling when we'll end.
1296
    // If we're not running, or time is not advancing for this animation, there's no telling when we'll end.
1298
    if (playState() != PlayState::Running)
1297
    auto playbackRate = effectivePlaybackRate();
1298
    if (playState() != PlayState::Running || !playbackRate)
1299
        return Seconds::infinity();
1299
        return Seconds::infinity();
1300
1300
1301
    // CSS Animations dispatch events for each iteration, so compute the time until
1301
    auto& effect = *this->effect();
1302
    // the end of this iteration. Any other animation only cares about remaning total time.
1302
    auto timing = effect.getBasicTiming();
1303
    if (isCSSAnimation()) {
1303
    switch (timing.phase) {
1304
        auto* animationEffect = effect();
1304
    case AnimationEffectPhase::Before:
1305
        auto timing = animationEffect->getComputedTiming();
1305
        // The animation is in its "before" phase, in this case we can wait until it enters its "active" phase.
1306
        // If we're actively running, we need the time until the next iteration.
1306
        return (effect.delay() - timing.localTime.value()) / playbackRate;
1307
        if (auto iterationProgress = timing.simpleIterationProgress)
1307
    case AnimationEffectPhase::Active:
1308
            return animationEffect->iterationDuration() * (1 - *iterationProgress);
1308
        // Non-accelerated animations in the "active" phase will need to update their animated value at the immediate next opportunity.
1309
1309
        if (!isRunningAccelerated())
1310
        // Otherwise we're probably in the before phase waiting to reach our start time.
1310
            return 0_s;
1311
        if (auto animationCurrentTime = currentTime()) {
1311
        // Accelerated CSS Animations need to trigger "animationiteration" events, in this case we can wait until the next iteration.
1312
            // If our current time is negative, we need to be scheduled to be resolved at the inverse
1312
        if (isCSSAnimation()) {
1313
            // of our current time, unless we fill backwards, in which case we want to invalidate as
1313
            if (auto iterationProgress = effect.getComputedTiming().simpleIterationProgress)
1314
            // soon as possible.
1314
                return effect.iterationDuration() * (1 - *iterationProgress);
1315
            auto localTime = animationCurrentTime.value();
1316
            if (localTime < 0_s)
1317
                return -localTime;
1318
            if (localTime < animationEffect->delay())
1319
                return animationEffect->delay() - localTime;
1320
        }
1315
        }
1321
    } else if (auto animationCurrentTime = currentTime())
1316
        // Accelerated animations in the "active" phase can wait until they ended.
1322
        return effect()->endTime() - *animationCurrentTime;
1317
        return (effect.endTime() - timing.localTime.value()) / playbackRate;
1318
    case AnimationEffectPhase::After:
1319
        // The animation is in its after phase, which means it will no longer update its value, so it doens't need a tick.
1320
        return Seconds::infinity();
1321
    case AnimationEffectPhase::Idle:
1322
        ASSERT_NOT_REACHED();
1323
        return Seconds::infinity();
1324
    }
1323
1325
1324
    ASSERT_NOT_REACHED();
1326
    ASSERT_NOT_REACHED();
1325
    return Seconds::infinity();
1327
    return Seconds::infinity();
- a/Source/WebCore/animation/WebAnimation.h -1 / +1 lines
Lines 154-160 private: a/Source/WebCore/animation/WebAnimation.h_sec1
154
    enum class AutoRewind : uint8_t { Yes, No };
154
    enum class AutoRewind : uint8_t { Yes, No };
155
    enum class TimeToRunPendingTask : uint8_t { NotScheduled, ASAP, WhenReady };
155
    enum class TimeToRunPendingTask : uint8_t { NotScheduled, ASAP, WhenReady };
156
156
157
    void timingDidChange(DidSeek, SynchronouslyNotify);
157
    void timingDidChange(DidSeek, SynchronouslyNotify, Silently = Silently::No);
158
    void updateFinishedState(DidSeek, SynchronouslyNotify);
158
    void updateFinishedState(DidSeek, SynchronouslyNotify);
159
    Seconds effectEndTime() const;
159
    Seconds effectEndTime() const;
160
    WebAnimation& readyPromiseResolve();
160
    WebAnimation& readyPromiseResolve();
- a/LayoutTests/ChangeLog +15 lines
Lines 1-3 a/LayoutTests/ChangeLog_sec1
1
2019-11-30  Antoine Quint  <graouts@apple.com>
2
3
        [Web Animations] Enable WebAnimationsCSSIntegrationEnabled by default
4
        https://bugs.webkit.org/show_bug.cgi?id=204650
5
        <rdar://problem/57534005>
6
7
        Reviewed by NOBODY (OOPS!).
8
9
        Adding tests checking that we don't schedule animation updates while filling for accelerated and non-accelerated animations alike.
10
11
        * webanimations/no-scheduling-while-filling-accelerated-expected.txt: Added.
12
        * webanimations/no-scheduling-while-filling-accelerated.html: Added.
13
        * webanimations/no-scheduling-while-filling-non-accelerated-expected.txt: Added.
14
        * webanimations/no-scheduling-while-filling-non-accelerated.html: Added.
15
1
2019-11-29  Commit Queue  <commit-queue@webkit.org>
16
2019-11-29  Commit Queue  <commit-queue@webkit.org>
2
17
3
        Unreviewed, rolling out r252944.
18
        Unreviewed, rolling out r252944.
- a/LayoutTests/webanimations/no-scheduling-while-filling-accelerated-expected.txt +3 lines
Line 0 a/LayoutTests/webanimations/no-scheduling-while-filling-accelerated-expected.txt_sec1
1
2
PASS There should not be more than one update made to the timeline after a forward-filling accelerated animation completes. 
3
- a/LayoutTests/webanimations/no-scheduling-while-filling-accelerated.html +45 lines
Line 0 a/LayoutTests/webanimations/no-scheduling-while-filling-accelerated.html_sec1
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<style>
5
6
#target {
7
    width: 100px;
8
    height: 100px;
9
    background-color: black;
10
}
11
12
</style>
13
</head>
14
<body>
15
<script src="../resources/testharness.js"></script>
16
<script src="../resources/testharnessreport.js"></script>
17
<div id="target"></div>
18
<script>
19
20
function waitNFrames(numberOfFrames, continuation)
21
{
22
    let elapsedFrames = 0;
23
    (function rAFCallback() {
24
        if (elapsedFrames++ >= numberOfFrames)
25
            continuation();
26
        else
27
            requestAnimationFrame(rAFCallback);
28
    })();
29
}
30
31
async_test(t => {
32
    const keyframes = { transform: ["translateX(50px)", "translateX(100px)"] };
33
    const timing = { duration: 50, fill: "forwards" };
34
    document.getElementById("target").animate(keyframes, timing).addEventListener("finish", event => {
35
        const numberOfTimelineInvalidationsWhenFinished = internals.numberOfAnimationTimelineInvalidations();
36
        waitNFrames(3, () => {
37
            assert_equals(internals.numberOfAnimationTimelineInvalidations(), numberOfTimelineInvalidationsWhenFinished + 1);
38
            t.done();
39
        });
40
    });
41
}, "There should not be more than one update made to the timeline after a forward-filling accelerated animation completes.");
42
43
</script>
44
</body>
45
</html>
- a/LayoutTests/webanimations/no-scheduling-while-filling-non-accelerated-expected.txt +3 lines
Line 0 a/LayoutTests/webanimations/no-scheduling-while-filling-non-accelerated-expected.txt_sec1
1
2
PASS There should not be any updates made to the timeline after a forward-filling animation completes. 
3
- a/LayoutTests/webanimations/no-scheduling-while-filling-non-accelerated.html +45 lines
Line 0 a/LayoutTests/webanimations/no-scheduling-while-filling-non-accelerated.html_sec1
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<style>
5
6
#target {
7
    width: 100px;
8
    height: 100px;
9
    background-color: black;
10
}
11
12
</style>
13
</head>
14
<body>
15
<script src="../resources/testharness.js"></script>
16
<script src="../resources/testharnessreport.js"></script>
17
<div id="target"></div>
18
<script>
19
20
function waitNFrames(numberOfFrames, continuation)
21
{
22
    let elapsedFrames = 0;
23
    (function rAFCallback() {
24
        if (elapsedFrames++ >= numberOfFrames)
25
            continuation();
26
        else
27
            requestAnimationFrame(rAFCallback);
28
    })();
29
}
30
31
async_test(t => {
32
    const keyframes = { marginLeft: ["50px", "100px"] };
33
    const timing = { duration: 50, fill: "forwards" };
34
    document.getElementById("target").animate(keyframes, timing).addEventListener("finish", event => {
35
        const numberOfTimelineInvalidationsWhenFinished = internals.numberOfAnimationTimelineInvalidations();
36
        waitNFrames(2, () => {
37
            assert_equals(internals.numberOfAnimationTimelineInvalidations(), numberOfTimelineInvalidationsWhenFinished);
38
            t.done();
39
        });
40
    });
41
}, "There should not be any updates made to the timeline after a forward-filling animation completes.");
42
43
</script>
44
</body>
45
</html>

Return to Bug 204650