Title: [277112] trunk
Revision
277112
Author
[email protected]
Date
2021-05-06 13:11:19 -0700 (Thu, 06 May 2021)

Log Message

CSS custom properties on pseudo elements background gradients causes infinite layout and high CPU load
https://bugs.webkit.org/show_bug.cgi?id=194332
<rdar://problem/47873895>

Reviewed by Simon Fraser.

Source/WebCore:

When a background-image uses a CSS custom property the resulting StyleGeneratedImage may not be the same
object as during prior style updates. This caused transitions to be triggered for all style updates for
such background-image values. To fix this, we modify the == operator for StyleGeneratedImage to use
arePointingToEqualData() with the CSSImageGeneratorValue member and added an == operator for the
CSSImageGeneratorValue class to call into the existing equals() methods. These equals() methods
are now overrides of the virtual CSSImageGeneratorValue method.

This change in behavior required a change in RenderElement::updateFillImages() to not only check whether
the images were identical, but to also check whether the renderer was registered as a client on the new
images. To do this, we add a new virtual hasClient() method on StyleImage.

Test: webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property.html

* css/CSSImageGeneratorValue.cpp:
(WebCore::CSSImageGeneratorValue::operator== const):
* css/CSSImageGeneratorValue.h:
* rendering/RenderElement.cpp:
(WebCore::RenderElement::updateFillImages):
* rendering/style/FillLayer.cpp:
(WebCore::FillLayer::imagesIdentical): Deleted.
* rendering/style/FillLayer.h:
* rendering/style/StyleCachedImage.cpp:
(WebCore::StyleCachedImage::hasClient const):
* rendering/style/StyleCachedImage.h:
* rendering/style/StyleGeneratedImage.cpp:
(WebCore::StyleGeneratedImage::operator== const):
(WebCore::StyleGeneratedImage::hasClient const):
* rendering/style/StyleGeneratedImage.h:
* rendering/style/StyleImage.h:
* rendering/style/StyleMultiImage.cpp:
(WebCore::StyleMultiImage::hasClient const):
* rendering/style/StyleMultiImage.h:

LayoutTests:

Add a test where an element with a background-image set to a CSS gradient using a custom property as a color
stop changes another property targeted by a transition to check that there is no background-image transition
generated.

* webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property-expected.txt: Added.
* webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (277111 => 277112)


--- trunk/LayoutTests/ChangeLog	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/LayoutTests/ChangeLog	2021-05-06 20:11:19 UTC (rev 277112)
@@ -1,3 +1,18 @@
+2021-05-06  Antoine Quint  <[email protected]>
+
+        CSS custom properties on pseudo elements background gradients causes infinite layout and high CPU load
+        https://bugs.webkit.org/show_bug.cgi?id=194332
+        <rdar://problem/47873895>
+
+        Reviewed by Simon Fraser.
+
+        Add a test where an element with a background-image set to a CSS gradient using a custom property as a color
+        stop changes another property targeted by a transition to check that there is no background-image transition
+        generated. 
+
+        * webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property-expected.txt: Added.
+        * webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property.html: Added.
+
 2021-05-06  Diego Pino Garcia  <[email protected]>
 
         [GLIB] Unreviewed test gardening. Update expectations after WPT re-import in r277073 and r277077.

Added: trunk/LayoutTests/webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property-expected.txt (0 => 277112)


--- trunk/LayoutTests/webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property-expected.txt	2021-05-06 20:11:19 UTC (rev 277112)
@@ -0,0 +1,3 @@
+
+PASS An element with a background-image set to a CSS gradient with a stop color set by a custom property does not yield a background-image transition when another CSS property changes.
+

Added: trunk/LayoutTests/webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property.html (0 => 277112)


--- trunk/LayoutTests/webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property.html	                        (rev 0)
+++ trunk/LayoutTests/webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property.html	2021-05-06 20:11:19 UTC (rev 277112)
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+
+:root {
+    --custom-color: blue;
+}
+
+.target {
+    width: 100px;
+    height: 100px;
+    background: linear-gradient(var(--custom-color), green);
+    transition-duration: 1s;
+}
+
+.target.transition {
+    margin-left: 100px;
+}
+
+</style>
+<body>
+<script src=""
+<script src=""
+<script>
+
+'use strict';
+
+promise_test(async test => {
+    const target = document.body.appendChild(document.createElement("div"));
+    target.classList.add("target");
+
+    await new Promise(requestAnimationFrame);
+    target.classList.add("transition");
+
+    const animations = target.getAnimations();
+    assert_equals(animations.length, 1, "There is only one animation applied to the target.");
+    assert_true(animations[0] instanceof CSSTransition, "The single animation applied to the target is a CSS transition.");
+    assert_equals(animations[0].transitionProperty, "margin-left", "The single CSS transition applied to the target is for the margin-left property.");
+}, `An element with a background-image set to a CSS gradient with a stop color set by a custom property does not yield a background-image transition when another CSS property changes.`);
+
+</script>
+</body>
\ No newline at end of file

Modified: trunk/Source/WebCore/ChangeLog (277111 => 277112)


--- trunk/Source/WebCore/ChangeLog	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/ChangeLog	2021-05-06 20:11:19 UTC (rev 277112)
@@ -1,3 +1,44 @@
+2021-05-06  Antoine Quint  <[email protected]>
+
+        CSS custom properties on pseudo elements background gradients causes infinite layout and high CPU load
+        https://bugs.webkit.org/show_bug.cgi?id=194332
+        <rdar://problem/47873895>
+
+        Reviewed by Simon Fraser.
+
+        When a background-image uses a CSS custom property the resulting StyleGeneratedImage may not be the same
+        object as during prior style updates. This caused transitions to be triggered for all style updates for
+        such background-image values. To fix this, we modify the == operator for StyleGeneratedImage to use
+        arePointingToEqualData() with the CSSImageGeneratorValue member and added an == operator for the
+        CSSImageGeneratorValue class to call into the existing equals() methods. These equals() methods
+        are now overrides of the virtual CSSImageGeneratorValue method.
+
+        This change in behavior required a change in RenderElement::updateFillImages() to not only check whether
+        the images were identical, but to also check whether the renderer was registered as a client on the new
+        images. To do this, we add a new virtual hasClient() method on StyleImage.
+
+        Test: webanimations/css-transition-element-with-gradient-background-image-and-css-custom-property.html
+
+        * css/CSSImageGeneratorValue.cpp:
+        (WebCore::CSSImageGeneratorValue::operator== const):
+        * css/CSSImageGeneratorValue.h:
+        * rendering/RenderElement.cpp:
+        (WebCore::RenderElement::updateFillImages):
+        * rendering/style/FillLayer.cpp:
+        (WebCore::FillLayer::imagesIdentical): Deleted.
+        * rendering/style/FillLayer.h:
+        * rendering/style/StyleCachedImage.cpp:
+        (WebCore::StyleCachedImage::hasClient const):
+        * rendering/style/StyleCachedImage.h:
+        * rendering/style/StyleGeneratedImage.cpp:
+        (WebCore::StyleGeneratedImage::operator== const):
+        (WebCore::StyleGeneratedImage::hasClient const):
+        * rendering/style/StyleGeneratedImage.h:
+        * rendering/style/StyleImage.h:
+        * rendering/style/StyleMultiImage.cpp:
+        (WebCore::StyleMultiImage::hasClient const):
+        * rendering/style/StyleMultiImage.h:
+
 2021-05-06  Philippe Normand  <[email protected]>
 
         [WebAudio][GStreamer] socketpair leaks

Modified: trunk/Source/WebCore/css/CSSImageGeneratorValue.cpp (277111 => 277112)


--- trunk/Source/WebCore/css/CSSImageGeneratorValue.cpp	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/css/CSSImageGeneratorValue.cpp	2021-05-06 20:11:19 UTC (rev 277112)
@@ -298,6 +298,35 @@
     }
 }
 
+bool CSSImageGeneratorValue::operator==(const CSSImageGeneratorValue& other) const
+{
+    if (classType() != other.classType())
+        return false;
+
+    switch (classType()) {
+    case CrossfadeClass:
+        return downcast<CSSCrossfadeValue>(*this).equals(downcast<CSSCrossfadeValue>(other));
+    case CanvasClass:
+        return downcast<CSSCanvasValue>(*this).equals(downcast<CSSCanvasValue>(other));
+    case FilterImageClass:
+        return downcast<CSSFilterImageValue>(*this).equals(downcast<CSSFilterImageValue>(other));
+    case LinearGradientClass:
+        return downcast<CSSLinearGradientValue>(*this).equals(downcast<CSSLinearGradientValue>(other));
+    case RadialGradientClass:
+        return downcast<CSSRadialGradientValue>(*this).equals(downcast<CSSRadialGradientValue>(other));
+    case ConicGradientClass:
+        return downcast<CSSConicGradientValue>(*this).equals(downcast<CSSConicGradientValue>(other));
+#if ENABLE(CSS_PAINTING_API)
+    case PaintImageClass:
+        return downcast<CSSPaintImageValue>(*this).equals(downcast<CSSPaintImageValue>(other));
+#endif
+    default:
+        ASSERT_NOT_REACHED();
+    }
+
+    return false;
+}
+
 bool CSSImageGeneratorValue::subimageIsPending(const CSSValue& value)
 {
     if (is<CSSImageValue>(value))

Modified: trunk/Source/WebCore/css/CSSImageGeneratorValue.h (277111 => 277112)


--- trunk/Source/WebCore/css/CSSImageGeneratorValue.h	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/css/CSSImageGeneratorValue.h	2021-05-06 20:11:19 UTC (rev 277112)
@@ -57,6 +57,8 @@
 
     void loadSubimages(CachedResourceLoader&, const ResourceLoaderOptions&);
 
+    bool operator==(const CSSImageGeneratorValue& other) const;
+
 protected:
     CSSImageGeneratorValue(ClassType);
 

Modified: trunk/Source/WebCore/rendering/RenderElement.cpp (277111 => 277112)


--- trunk/Source/WebCore/rendering/RenderElement.cpp	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/RenderElement.cpp	2021-05-06 20:11:19 UTC (rev 277112)
@@ -353,10 +353,31 @@
 
 void RenderElement::updateFillImages(const FillLayer* oldLayers, const FillLayer& newLayers)
 {
-    // Optimize the common case.
-    if (FillLayer::imagesIdentical(oldLayers, &newLayers))
+    auto fillImagesAreIdentical = [](const FillLayer* layer1, const FillLayer* layer2) -> bool {
+        if (layer1 == layer2)
+            return true;
+
+        for (; layer1 && layer2; layer1 = layer1->next(), layer2 = layer2->next()) {
+            if (!arePointingToEqualData(layer1->image(), layer2->image()))
+                return false;
+        }
+
+        return !layer1 && !layer2;
+    };
+
+    auto isRegisteredWithNewFillImages = [&]() -> bool {
+        for (auto* layer = &newLayers; layer; layer = layer->next()) {
+            if (layer->image() && !layer->image()->hasClient(*this))
+                return false;
+        }
+        return true;
+    };
+
+    // If images have the same characteristics and this element is already registered as a
+    // client to the new images, there is nothing to do.
+    if (fillImagesAreIdentical(oldLayers, &newLayers) && isRegisteredWithNewFillImages())
         return;
-    
+
     // Add before removing, to avoid removing all clients of an image that is in both sets.
     for (auto* layer = &newLayers; layer; layer = layer->next()) {
         if (layer->image())

Modified: trunk/Source/WebCore/rendering/style/FillLayer.cpp (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/FillLayer.cpp	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/FillLayer.cpp	2021-05-06 20:11:19 UTC (rev 277112)
@@ -396,19 +396,6 @@
     return false;
 }
 
-bool FillLayer::imagesIdentical(const FillLayer* layer1, const FillLayer* layer2)
-{
-    if (layer1 == layer2)
-        return true;
-
-    for (; layer1 && layer2; layer1 = layer1->next(), layer2 = layer2->next()) {
-        if (!arePointingToEqualData(layer1->image(), layer2->image()))
-            return false;
-    }
-
-    return !layer1 && !layer2;
-}
-
 TextStream& operator<<(TextStream& ts, FillSize fillSize)
 {
     return ts << fillSize.type << " " << fillSize.size;

Modified: trunk/Source/WebCore/rendering/style/FillLayer.h (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/FillLayer.h	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/FillLayer.h	2021-05-06 20:11:19 UTC (rev 277112)
@@ -159,8 +159,6 @@
     void fillUnsetProperties();
     void cullEmptyLayers();
 
-    static bool imagesIdentical(const FillLayer*, const FillLayer*);
-
     static FillAttachment initialFillAttachment(FillLayerType) { return FillAttachment::ScrollBackground; }
     static FillBox initialFillClip(FillLayerType) { return FillBox::Border; }
     static FillBox initialFillOrigin(FillLayerType type) { return type == FillLayerType::Background ? FillBox::Padding : FillBox::Border; }

Modified: trunk/Source/WebCore/rendering/style/StyleCachedImage.cpp (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/StyleCachedImage.cpp	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/StyleCachedImage.cpp	2021-05-06 20:11:19 UTC (rev 277112)
@@ -172,6 +172,14 @@
     m_cachedImage->removeClient(renderer);
 }
 
+bool StyleCachedImage::hasClient(RenderElement& renderer) const
+{
+    ASSERT(!m_isPending);
+    if (!m_cachedImage)
+        return false;
+    return m_cachedImage->hasClient(renderer);
+}
+
 RefPtr<Image> StyleCachedImage::image(RenderElement* renderer, const FloatSize&) const
 {
     ASSERT(!m_isPending);

Modified: trunk/Source/WebCore/rendering/style/StyleCachedImage.h (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/StyleCachedImage.h	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/StyleCachedImage.h	2021-05-06 20:11:19 UTC (rev 277112)
@@ -60,6 +60,7 @@
     void setContainerContextForRenderer(const RenderElement&, const FloatSize&, float) final;
     void addClient(RenderElement&) final;
     void removeClient(RenderElement&) final;
+    bool hasClient(RenderElement&) const final;
     RefPtr<Image> image(RenderElement*, const FloatSize&) const final;
     float imageScaleFactor() const final;
     bool knownToBeOpaque(const RenderElement&) const final;

Modified: trunk/Source/WebCore/rendering/style/StyleGeneratedImage.cpp (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/StyleGeneratedImage.cpp	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/StyleGeneratedImage.cpp	2021-05-06 20:11:19 UTC (rev 277112)
@@ -37,6 +37,13 @@
     m_isGeneratedImage = true;
 }
 
+bool StyleGeneratedImage::operator==(const StyleImage& other) const
+{
+    if (is<StyleGeneratedImage>(other))
+        return arePointingToEqualData(m_imageGeneratorValue.ptr(), downcast<StyleGeneratedImage>(other).m_imageGeneratorValue.ptr());
+    return false;
+}
+
 Ref<CSSValue> StyleGeneratedImage::cssValue() const
 {
     return m_imageGeneratorValue.copyRef();
@@ -96,6 +103,11 @@
     m_imageGeneratorValue->removeClient(renderer);
 }
 
+bool StyleGeneratedImage::hasClient(RenderElement& renderer) const
+{
+    return m_imageGeneratorValue->clients().contains(&renderer);
+}
+
 RefPtr<Image> StyleGeneratedImage::image(RenderElement* renderer, const FloatSize& size) const
 {
     return renderer ? m_imageGeneratorValue->image(*renderer, size) : &Image::nullImage();

Modified: trunk/Source/WebCore/rendering/style/StyleGeneratedImage.h (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/StyleGeneratedImage.h	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/StyleGeneratedImage.h	2021-05-06 20:11:19 UTC (rev 277112)
@@ -40,7 +40,7 @@
     CSSImageGeneratorValue& imageValue() { return m_imageGeneratorValue; }
 
 private:
-    bool operator==(const StyleImage& other) const final { return data() == other.data(); }
+    bool operator==(const StyleImage& other) const final;
 
     WrappedImagePtr data() const final { return m_imageGeneratorValue.ptr(); }
 
@@ -56,6 +56,7 @@
     void setContainerContextForRenderer(const RenderElement&, const FloatSize& containerSize, float) final { m_containerSize = containerSize; }
     void addClient(RenderElement&) final;
     void removeClient(RenderElement&) final;
+    bool hasClient(RenderElement&) const final;
     RefPtr<Image> image(RenderElement*, const FloatSize&) const final;
     bool knownToBeOpaque(const RenderElement&) const final;
 

Modified: trunk/Source/WebCore/rendering/style/StyleImage.h (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/StyleImage.h	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/StyleImage.h	2021-05-06 20:11:19 UTC (rev 277112)
@@ -62,6 +62,7 @@
     virtual void setContainerContextForRenderer(const RenderElement&, const FloatSize&, float) = 0;
     virtual void addClient(RenderElement&) = 0;
     virtual void removeClient(RenderElement&) = 0;
+    virtual bool hasClient(RenderElement&) const = 0;
     virtual RefPtr<Image> image(RenderElement*, const FloatSize&) const = 0;
     virtual WrappedImagePtr data() const = 0;
     virtual float imageScaleFactor() const { return 1; }

Modified: trunk/Source/WebCore/rendering/style/StyleMultiImage.cpp (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/StyleMultiImage.cpp	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/StyleMultiImage.cpp	2021-05-06 20:11:19 UTC (rev 277112)
@@ -148,6 +148,13 @@
     m_selectedImage->removeClient(renderer);
 }
 
+bool StyleMultiImage::hasClient(RenderElement& renderer) const
+{
+    if (!m_selectedImage)
+        return false;
+    return m_selectedImage->hasClient(renderer);
+}
+
 RefPtr<Image> StyleMultiImage::image(RenderElement* renderer, const FloatSize& size) const
 {
     if (!m_selectedImage)

Modified: trunk/Source/WebCore/rendering/style/StyleMultiImage.h (277111 => 277112)


--- trunk/Source/WebCore/rendering/style/StyleMultiImage.h	2021-05-06 19:41:53 UTC (rev 277111)
+++ trunk/Source/WebCore/rendering/style/StyleMultiImage.h	2021-05-06 20:11:19 UTC (rev 277112)
@@ -59,6 +59,7 @@
     void setContainerContextForRenderer(const RenderElement&, const FloatSize&, float);
     void addClient(RenderElement&) final;
     void removeClient(RenderElement&) final;
+    bool hasClient(RenderElement&) const final;
     RefPtr<Image> image(RenderElement*, const FloatSize&) const final;
     float imageScaleFactor() const final;
     bool knownToBeOpaque(const RenderElement&) const final;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to