Bug 209370 - REGRESSION: CSS animations inside an embedded SVG image do not animate
Summary: REGRESSION: CSS animations inside an embedded SVG image do not animate
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Animations (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Said Abou-Hallawa
URL:
Keywords: BrowserCompat, InRadar
: 211922 (view as bug list)
Depends on:
Blocks:
 
Reported: 2020-03-20 18:21 PDT by tym46383
Modified: 2020-05-18 20:12 PDT (History)
21 users (show)

See Also:


Attachments
Test (229 bytes, text/html)
2020-04-03 08:36 PDT, Antoine Quint
no flags Details
Patch (4.89 KB, patch)
2020-04-03 17:22 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (9.83 KB, patch)
2020-04-03 21:43 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (15.27 KB, patch)
2020-04-07 01:16 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (15.19 KB, patch)
2020-04-07 01:29 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (21.15 KB, patch)
2020-04-07 12:25 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (19.93 KB, patch)
2020-04-07 15:31 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (22.78 KB, patch)
2020-04-08 13:07 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (23.01 KB, patch)
2020-04-08 13:30 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (26.66 KB, patch)
2020-04-08 21:10 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (26.52 KB, patch)
2020-04-09 12:03 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff
Patch (26.75 KB, patch)
2020-04-09 12:22 PDT, Said Abou-Hallawa
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description tym46383 2020-03-20 18:21:26 PDT
On this site, https://css-tricks.com/having-fun-with-link-hover-effects/, the wavy line a:hover effect is supposed to animate but it doesn't. The rest of the examples provided all seem to animate correctly otherwise.

This page mentions its inspiration as coming from https://theoutline.com/post/5970/unconventional-wisdom-emotional-readiness-is-bullshit. The hover effects here don't work, either. I thought that this issue might only apply to link states, but none of the decorative lines animate either. They do in Firefox.

This happens in the WebKit Nightly as well as on iPadOS 13.4 beta 6. I tried adding the -webkit prefix but the results are the same.
Comment 1 tym46383 2020-03-20 18:35:56 PDT
(In reply to tym46383 from comment #0)
> On this site, https://css-tricks.com/having-fun-with-link-hover-effects/,
> the wavy line a:hover effect is supposed to animate but it doesn't. The rest
> of the examples provided all seem to animate correctly otherwise.
> 
> This page mentions its inspiration as coming from
> https://theoutline.com/post/5970/unconventional-wisdom-emotional-readiness-
> is-bullshit. The hover effects here don't work, either. I thought that this
> issue might only apply to link states, but none of the decorative lines
> animate either. They do in Firefox.
> 
> This happens in the WebKit Nightly as well as on iPadOS 13.4 beta 6. I tried
> adding the -webkit prefix but the results are the same.

Actually, I just noticed that the horizontal "Border to Background Effect" (https://codepen.io/geoffgraham/pen/qMbVGv) fades from orange to bluish-gray in WebKit before fading to white. In Firefox it goes from orange to white. But this and the rest of the examples aren't SVG animations so it's not especially relevant to this bug I suppose.
Comment 2 Radar WebKit Bug Importer 2020-03-21 18:51:38 PDT
<rdar://problem/60733068>
Comment 3 Antoine Quint 2020-03-23 10:21:17 PDT
https://theoutline.com/post/5970/unconventional-wisdom-emotional-readiness-is-bullshit shows the issue without hovering links right at the top of the page where I can see the dividing wavy line animate in Firefox but not in STP 102. This is not new with the Web Animations implementation.
Comment 4 Antoine Quint 2020-03-23 10:28:28 PDT
The animation is performed by a CSS animation in an SVG document, https://theoutline.com/svg/sq/v?c=ffffff&a=4&f=5&sw=1&anim=true. You can change the store color by modifying the URL to https://theoutline.com/svg/sq/v?c=000000&a=4&f=5&sw=1&anim=true, and this shows the animation working as expected. This is probably an issue where SVG images in background-image don't run animations.
Comment 5 TG 2020-03-29 13:58:12 PDT
Confirming this bug in Safari 13.1 (15609.1.20.111.8) and STP Release 103. SVG keyframe animations on CSS background-image attributes aren't rendering properly, but the animations will render if the SVG is opened/displayed directly in a browser window.
Comment 6 Antoine Quint 2020-04-03 02:12:09 PDT
This is a regression due to Web Animations introduced in Safari 13.1. Looking for a fix now.
Comment 7 Antoine Quint 2020-04-03 05:48:04 PDT
The DocumentTimeline schedules an initial update but its current time never advances.
Comment 8 Antoine Quint 2020-04-03 06:01:26 PDT
In DisplayRefreshMonitorMac::requestRefreshCallback():

        CVReturn error = CVDisplayLinkCreateWithCGDisplay(displayID(), &m_displayLink);
        if (error)
            return false;

We get an error and bail early, never registering for an actual callback.
Comment 9 Antoine Quint 2020-04-03 06:02:29 PDT
The legacy animation engine uses a different codepath presumably, since DisplayRefreshMonitorMac::requestRefreshCallback() is never called.
Comment 10 Antoine Quint 2020-04-03 06:15:46 PDT
The displayID passed to CVDisplayLinkCreateWithCGDisplay is 0, presumably that's an invalid value. Maybe this is because this is the Page for an SVGDocument?
Comment 11 Antoine Quint 2020-04-03 07:13:01 PDT
When I open the SVG file by itself, the code never goes through DisplayRefreshMonitorMac::requestRefreshCallback() in Source/WebCore/platform/graphics/mac/DisplayRefreshMonitorMac.cpp
Comment 12 Antoine Quint 2020-04-03 08:24:31 PDT
This also affects <img> elements pointing to SVG documents.
Comment 13 Antoine Quint 2020-04-03 08:36:00 PDT
A simple reduction:

<img src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><style>@keyframes animate { to { opacity: 0 } } rect { animation: animate 0.5s infinite alternate }</style><rect width="100%" height="100%"></rect></svg>'>
Comment 14 Antoine Quint 2020-04-03 08:36:33 PDT
Created attachment 395379 [details]
Test
Comment 15 Said Abou-Hallawa 2020-04-03 10:59:48 PDT
The CSS animation DocumentTimeline is added to the SVGDocument of the embedded SVGImage. This SVGDocument is not enumerate-able by Page::forEachDocument(), so we never call updateAnimationsAndSendEvents() for it.

I am not sure what the right solution is: 
1. Should we add the DocumentTimeline to the topDocument?
2. Should we make Page::forEachDocument() enumerate through the SVGDocuments of the embedded SVGImages also?
Comment 16 Antoine Quint 2020-04-03 11:21:31 PDT
(In reply to Said Abou-Hallawa from comment #15)
> The CSS animation DocumentTimeline is added to the SVGDocument of the
> embedded SVGImage. This SVGDocument is not enumerate-able by
> Page::forEachDocument(), so we never call updateAnimationsAndSendEvents()
> for it.
> 
> I am not sure what the right solution is: 
> 1. Should we add the DocumentTimeline to the topDocument?
> 2. Should we make Page::forEachDocument() enumerate through the SVGDocuments
> of the embedded SVGImages also?

I've been talking about this with Simon and he reckons what we need to do is to make Chrome::displayID() return the hosting page's displayID such that the call to CVDisplayLinkCreateWithCGDisplay doesn't fail.
Comment 17 Simon Fraser (smfr) 2020-04-03 12:33:07 PDT
I think SVGImageChromeClient should implement something that allows the ImageObserver to implement schduleRenderingUpdate.
Comment 18 Said Abou-Hallawa 2020-04-03 13:02:00 PDT
(In reply to Antoine Quint from comment #16)
> (In reply to Said Abou-Hallawa from comment #15)
> > The CSS animation DocumentTimeline is added to the SVGDocument of the
> > embedded SVGImage. This SVGDocument is not enumerate-able by
> > Page::forEachDocument(), so we never call updateAnimationsAndSendEvents()
> > for it.
> > 
> > I am not sure what the right solution is: 
> > 1. Should we add the DocumentTimeline to the topDocument?
> > 2. Should we make Page::forEachDocument() enumerate through the SVGDocuments
> > of the embedded SVGImages also?
> 
> I've been talking about this with Simon and he reckons what we need to do is
> to make Chrome::displayID() return the hosting page's displayID such that
> the call to CVDisplayLinkCreateWithCGDisplay doesn't fail.

DisplayRefreshMonitorMac is only used by WK1 and the bug is happening in WK2 as well.
Comment 19 Said Abou-Hallawa 2020-04-03 17:22:41 PDT
Created attachment 395420 [details]
Patch
Comment 20 Said Abou-Hallawa 2020-04-03 17:32:46 PDT
Comment on attachment 395420 [details]
Patch

Something is still missing in this patch. WebAnimation should be enabled for top-level SVGDocument but not for the embedded ones.
Comment 21 Simon Fraser (smfr) 2020-04-03 18:07:29 PDT
Comment on attachment 395420 [details]
Patch

This doesn't seem like the right approach (and we also want to eliminate the old animation code eventually).
Comment 22 Said Abou-Hallawa 2020-04-03 21:43:37 PDT
Created attachment 395432 [details]
Patch
Comment 23 Said Abou-Hallawa 2020-04-03 21:45:35 PDT
(In reply to Said Abou-Hallawa from comment #22)
> Created attachment 395432 [details]
> Patch

This patch is not the right approach. But it is fixes bug and it does not break the top-level case. Besides it has layout tests. Just ignore it for now.
Comment 24 Said Abou-Hallawa 2020-04-07 01:16:30 PDT
Created attachment 395659 [details]
Patch
Comment 25 Said Abou-Hallawa 2020-04-07 01:29:08 PDT
Created attachment 395662 [details]
Patch
Comment 26 Said Abou-Hallawa 2020-04-07 10:26:55 PDT
Comment on attachment 395662 [details]
Patch

Two problems with this patch:
1. It fixes the case where the SVG image is embedded in an <img> element. But it does not fix the case where the SVG image is the background of an element.
2. It has to enumerate the RendererImages of every document in each Page::updateRendering() regardless they have SVG images or not.
Comment 27 Said Abou-Hallawa 2020-04-07 12:25:15 PDT
Created attachment 395723 [details]
Patch
Comment 28 Simon Fraser (smfr) 2020-04-07 12:30:13 PDT
Comment on attachment 395723 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=395723&action=review

> Source/WebCore/page/Page.cpp:1312
> +    if (chrome().client().isSVGImageChromeClient())
> +        chrome().client().scheduleRenderingUpdate();

This is weird, because ChromeClient is a virtual interface. Maybe Chrome::scheduleRenderingUpdate() could just return a bool  - true if it handled it.

> Source/WebCore/page/Page.cpp:1374
> +        for (auto& renderer : descendantsOfType<RenderElement>(*renderView))
> +            renderer.updateRendering();

This is a massive tree walk! I think we need to avoid this by storing a list of SVGImages somewhere.
Comment 29 Said Abou-Hallawa 2020-04-07 15:31:21 PDT
Created attachment 395750 [details]
Patch
Comment 30 Simon Fraser (smfr) 2020-04-07 20:33:28 PDT
Comment on attachment 395750 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=395750&action=review

> Source/WebCore/dom/Document.cpp:6401
> +    for (auto& cachedResourceHandle : m_cachedResourceLoader->allCachedResources().values()) {
> +        auto* cachedResource = cachedResourceHandle.get();
> +        if (!cachedResource || !cachedResource->isImage())
> +            continue;
> +        
> +        auto* image = downcast<CachedImage>(cachedResource)->image();
> +        if (!image || !is<SVGImage>(image))
> +            continue;
> +
> +        if (auto* page = downcast<SVGImage>(image)->page())
> +            page->updateRendering();
> +    }

Please measure how long this takes on some resource-heavy pages (throw in a TimingScope in your local release build). It might be too expensive to do every frame.

> Source/WebCore/page/Page.cpp:1311
> +    if (chrome().client().isSVGImageChromeClient())
> +        chrome().client().scheduleRenderingUpdate();

Can we do this through virtual functions, as I suggested before?
Comment 31 Said Abou-Hallawa 2020-04-08 13:07:40 PDT
Created attachment 395859 [details]
Patch
Comment 32 Said Abou-Hallawa 2020-04-08 13:13:39 PDT
Comment on attachment 395750 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=395750&action=review

>> Source/WebCore/dom/Document.cpp:6401
>> +    }
> 
> Please measure how long this takes on some resource-heavy pages (throw in a TimingScope in your local release build). It might be too expensive to do every frame.

I changed that by building a HashSet<String> which contains the URLs of the cached SVGImages when the documentDidFinishLoadEvent fires. This HashSet<> will be used to get the list of the SVGImages in a document instead. So building HashSet<String> happens only once and enumerating this HashSet is cheap. If the HashSet is empty, which is the common case, there should not be any noticeable overhead in the Page::updateRendering().

>> Source/WebCore/page/Page.cpp:1311
>> +        chrome().client().scheduleRenderingUpdate();
> 
> Can we do this through virtual functions, as I suggested before?

Done.
Comment 33 Said Abou-Hallawa 2020-04-08 13:30:26 PDT
Created attachment 395860 [details]
Patch
Comment 34 Said Abou-Hallawa 2020-04-08 13:42:37 PDT
Comment on attachment 395860 [details]
Patch

This patch does not fix the animation in https://css-tricks.com/having-fun-with-link-hover-effects/.
Comment 35 Said Abou-Hallawa 2020-04-08 21:10:31 PDT
Created attachment 395907 [details]
Patch
Comment 36 Simon Fraser (smfr) 2020-04-09 10:33:50 PDT
Comment on attachment 395907 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=395907&action=review

> Source/WebCore/loader/cache/CachedResourceLoader.h:121
> +    Vector<SVGImage*> allCachedSVGImages() const;

I think this should return Vector<Ref<SVGImage>>

> Source/WebCore/page/ChromeClient.h:319
> +    virtual bool scheduleTimedRenderingUpdate() { return false; }

Why do we need to expose the "timed" version here?

> Source/WebCore/page/Page.cpp:1370
> +        for (auto* image : document.cachedResourceLoader().allCachedSVGImages()) {

This becomes for (auto image : ...

> Source/WebCore/svg/graphics/SVGImage.h:72
> +    Page* page() { return m_page.get(); }

Because this is so special, I would call it internalPage() or something. It's not the Page that the image lives in.
Comment 37 Said Abou-Hallawa 2020-04-09 12:03:43 PDT
Created attachment 395986 [details]
Patch
Comment 38 Said Abou-Hallawa 2020-04-09 12:10:17 PDT
Comment on attachment 395907 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=395907&action=review

>> Source/WebCore/loader/cache/CachedResourceLoader.h:121
>> +    Vector<SVGImage*> allCachedSVGImages() const;
> 
> I think this should return Vector<Ref<SVGImage>>

Done.

>> Source/WebCore/page/ChromeClient.h:319
>> +    virtual bool scheduleTimedRenderingUpdate() { return false; }
> 
> Why do we need to expose the "timed" version here?

Because the immediate scheduleRenderingUpdate() is used by RenderLayerCompositor. All the callers including the WebAnimation use scheduleTimedRenderingUpdate(). The immediate scheduling was implemented to fix many layout test flakiness where waiting for the next frame was producing incorrect results.

>> Source/WebCore/page/Page.cpp:1370
>> +        for (auto* image : document.cachedResourceLoader().allCachedSVGImages()) {
> 
> This becomes for (auto image : ...

But I had to make it for (auto& image : ... because Ref<SVGImage> can't be copied.

>> Source/WebCore/svg/graphics/SVGImage.h:72
>> +    Page* page() { return m_page.get(); }
> 
> Because this is so special, I would call it internalPage() or something. It's not the Page that the image lives in.

Done: page() => internalPage().
Comment 39 Said Abou-Hallawa 2020-04-09 12:22:51 PDT
Created attachment 395988 [details]
Patch
Comment 40 EWS 2020-04-09 14:46:02 PDT
Committed r259830: <https://trac.webkit.org/changeset/259830>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 395988 [details].
Comment 41 Said Abou-Hallawa 2020-05-18 20:12:09 PDT
*** Bug 211922 has been marked as a duplicate of this bug. ***