RESOLVED FIXED 209370
REGRESSION: CSS animations inside an embedded SVG image do not animate
https://bugs.webkit.org/show_bug.cgi?id=209370
Summary REGRESSION: CSS animations inside an embedded SVG image do not animate
tym46383
Reported 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.
Attachments
Test (229 bytes, text/html)
2020-04-03 08:36 PDT, Antoine Quint
no flags
Patch (4.89 KB, patch)
2020-04-03 17:22 PDT, Said Abou-Hallawa
no flags
Patch (9.83 KB, patch)
2020-04-03 21:43 PDT, Said Abou-Hallawa
no flags
Patch (15.27 KB, patch)
2020-04-07 01:16 PDT, Said Abou-Hallawa
no flags
Patch (15.19 KB, patch)
2020-04-07 01:29 PDT, Said Abou-Hallawa
no flags
Patch (21.15 KB, patch)
2020-04-07 12:25 PDT, Said Abou-Hallawa
no flags
Patch (19.93 KB, patch)
2020-04-07 15:31 PDT, Said Abou-Hallawa
no flags
Patch (22.78 KB, patch)
2020-04-08 13:07 PDT, Said Abou-Hallawa
no flags
Patch (23.01 KB, patch)
2020-04-08 13:30 PDT, Said Abou-Hallawa
no flags
Patch (26.66 KB, patch)
2020-04-08 21:10 PDT, Said Abou-Hallawa
no flags
Patch (26.52 KB, patch)
2020-04-09 12:03 PDT, Said Abou-Hallawa
no flags
Patch (26.75 KB, patch)
2020-04-09 12:22 PDT, Said Abou-Hallawa
no flags
tym46383
Comment 1 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.
Radar WebKit Bug Importer
Comment 2 2020-03-21 18:51:38 PDT
Antoine Quint
Comment 3 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.
Antoine Quint
Comment 4 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.
TG
Comment 5 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.
Antoine Quint
Comment 6 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.
Antoine Quint
Comment 7 2020-04-03 05:48:04 PDT
The DocumentTimeline schedules an initial update but its current time never advances.
Antoine Quint
Comment 8 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.
Antoine Quint
Comment 9 2020-04-03 06:02:29 PDT
The legacy animation engine uses a different codepath presumably, since DisplayRefreshMonitorMac::requestRefreshCallback() is never called.
Antoine Quint
Comment 10 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?
Antoine Quint
Comment 11 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
Antoine Quint
Comment 12 2020-04-03 08:24:31 PDT
This also affects <img> elements pointing to SVG documents.
Antoine Quint
Comment 13 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>'>
Antoine Quint
Comment 14 2020-04-03 08:36:33 PDT
Said Abou-Hallawa
Comment 15 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?
Antoine Quint
Comment 16 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.
Simon Fraser (smfr)
Comment 17 2020-04-03 12:33:07 PDT
I think SVGImageChromeClient should implement something that allows the ImageObserver to implement schduleRenderingUpdate.
Said Abou-Hallawa
Comment 18 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.
Said Abou-Hallawa
Comment 19 2020-04-03 17:22:41 PDT
Said Abou-Hallawa
Comment 20 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.
Simon Fraser (smfr)
Comment 21 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).
Said Abou-Hallawa
Comment 22 2020-04-03 21:43:37 PDT
Said Abou-Hallawa
Comment 23 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.
Said Abou-Hallawa
Comment 24 2020-04-07 01:16:30 PDT
Said Abou-Hallawa
Comment 25 2020-04-07 01:29:08 PDT
Said Abou-Hallawa
Comment 26 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.
Said Abou-Hallawa
Comment 27 2020-04-07 12:25:15 PDT
Simon Fraser (smfr)
Comment 28 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.
Said Abou-Hallawa
Comment 29 2020-04-07 15:31:21 PDT
Simon Fraser (smfr)
Comment 30 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?
Said Abou-Hallawa
Comment 31 2020-04-08 13:07:40 PDT
Said Abou-Hallawa
Comment 32 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.
Said Abou-Hallawa
Comment 33 2020-04-08 13:30:26 PDT
Said Abou-Hallawa
Comment 34 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/.
Said Abou-Hallawa
Comment 35 2020-04-08 21:10:31 PDT
Simon Fraser (smfr)
Comment 36 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.
Said Abou-Hallawa
Comment 37 2020-04-09 12:03:43 PDT
Said Abou-Hallawa
Comment 38 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().
Said Abou-Hallawa
Comment 39 2020-04-09 12:22:51 PDT
EWS
Comment 40 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].
Said Abou-Hallawa
Comment 41 2020-05-18 20:12:09 PDT
*** Bug 211922 has been marked as a duplicate of this bug. ***
Note You need to log in before you can comment on or make changes to this bug.