source: webkit/trunk/Source/WebCore/css/CSSImageGeneratorValue.cpp

Last change on this file was 277112, checked in by [email protected], 4 years ago

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.
  • Property svn:eol-style set to native
File size: 12.7 KB
Line 
1/*
2 * Copyright (C) 2008, 2011, 2012, 2013 Apple Inc. All rights reserved.
3 * Copyright (C) 2013 Adobe Systems Incorporated. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "CSSImageGeneratorValue.h"
29
30#include "CSSCanvasValue.h"
31#include "CSSCrossfadeValue.h"
32#include "CSSFilterImageValue.h"
33#include "CSSGradientValue.h"
34#include "CSSImageValue.h"
35#include "CSSNamedImageValue.h"
36#include "CSSPaintImageValue.h"
37#include "GeneratedImage.h"
38#include "HTMLCanvasElement.h"
39#include "InspectorInstrumentation.h"
40#include "RenderElement.h"
41
42namespace WebCore {
43
44static const Seconds timeToKeepCachedGeneratedImages { 3_s };
45
46class CSSImageGeneratorValue::CachedGeneratedImage {
47 WTF_MAKE_FAST_ALLOCATED;
48public:
49 CachedGeneratedImage(CSSImageGeneratorValue&, FloatSize, GeneratedImage&);
50 GeneratedImage& image() const { return m_image; }
51 void puntEvictionTimer() { m_evictionTimer.restart(); }
52
53private:
54 void evictionTimerFired();
55
56 CSSImageGeneratorValue& m_owner;
57 const FloatSize m_size;
58 const Ref<GeneratedImage> m_image;
59 DeferrableOneShotTimer m_evictionTimer;
60};
61
62CSSImageGeneratorValue::CSSImageGeneratorValue(ClassType classType)
63 : CSSValue(classType)
64{
65}
66
67CSSImageGeneratorValue::~CSSImageGeneratorValue() = default;
68
69void CSSImageGeneratorValue::addClient(RenderElement& renderer)
70{
71 if (m_clients.isEmpty())
72 ref();
73
74 m_clients.add(&renderer);
75
76 if (is<CSSCanvasValue>(this)) {
77 if (HTMLCanvasElement* canvasElement = downcast<CSSCanvasValue>(this)->element())
78 InspectorInstrumentation::didChangeCSSCanvasClientNodes(*canvasElement);
79 }
80}
81
82void CSSImageGeneratorValue::removeClient(RenderElement& renderer)
83{
84 ASSERT(m_clients.contains(&renderer));
85 if (!m_clients.remove(&renderer))
86 return;
87
88 if (is<CSSCanvasValue>(this)) {
89 if (HTMLCanvasElement* canvasElement = downcast<CSSCanvasValue>(this)->element())
90 InspectorInstrumentation::didChangeCSSCanvasClientNodes(*canvasElement);
91 }
92
93 if (m_clients.isEmpty())
94 deref();
95}
96
97GeneratedImage* CSSImageGeneratorValue::cachedImageForSize(FloatSize size)
98{
99 if (size.isEmpty())
100 return nullptr;
101
102 auto* cachedGeneratedImage = m_images.get(size);
103 if (!cachedGeneratedImage)
104 return nullptr;
105
106 cachedGeneratedImage->puntEvictionTimer();
107 return &cachedGeneratedImage->image();
108}
109
110void CSSImageGeneratorValue::saveCachedImageForSize(FloatSize size, GeneratedImage& image)
111{
112 ASSERT(!m_images.contains(size));
113 m_images.add(size, makeUnique<CachedGeneratedImage>(*this, size, image));
114}
115
116void CSSImageGeneratorValue::evictCachedGeneratedImage(FloatSize size)
117{
118 ASSERT(m_images.contains(size));
119 m_images.remove(size);
120}
121
122inline CSSImageGeneratorValue::CachedGeneratedImage::CachedGeneratedImage(CSSImageGeneratorValue& owner, FloatSize size, GeneratedImage& image)
123 : m_owner(owner)
124 , m_size(size)
125 , m_image(image)
126 , m_evictionTimer(*this, &CSSImageGeneratorValue::CachedGeneratedImage::evictionTimerFired, timeToKeepCachedGeneratedImages)
127{
128 m_evictionTimer.restart();
129}
130
131void CSSImageGeneratorValue::CachedGeneratedImage::evictionTimerFired()
132{
133 // NOTE: This is essentially a "delete this", the object is no longer valid after this line.
134 m_owner.evictCachedGeneratedImage(m_size);
135}
136
137RefPtr<Image> CSSImageGeneratorValue::image(RenderElement& renderer, const FloatSize& size)
138{
139 switch (classType()) {
140 case CanvasClass:
141 return downcast<CSSCanvasValue>(*this).image(renderer, size);
142 case NamedImageClass:
143 return downcast<CSSNamedImageValue>(*this).image(renderer, size);
144 case CrossfadeClass:
145 return downcast<CSSCrossfadeValue>(*this).image(renderer, size);
146 case FilterImageClass:
147 return downcast<CSSFilterImageValue>(*this).image(renderer, size);
148 case LinearGradientClass:
149 return downcast<CSSLinearGradientValue>(*this).image(renderer, size);
150 case RadialGradientClass:
151 return downcast<CSSRadialGradientValue>(*this).image(renderer, size);
152 case ConicGradientClass:
153 return downcast<CSSConicGradientValue>(*this).image(renderer, size);
154#if ENABLE(CSS_PAINTING_API)
155 case PaintImageClass:
156 return downcast<CSSPaintImageValue>(*this).image(renderer, size);
157#endif
158 default:
159 ASSERT_NOT_REACHED();
160 }
161 return nullptr;
162}
163
164bool CSSImageGeneratorValue::isFixedSize() const
165{
166 switch (classType()) {
167 case CanvasClass:
168 return downcast<CSSCanvasValue>(*this).isFixedSize();
169 case NamedImageClass:
170 return downcast<CSSNamedImageValue>(*this).isFixedSize();
171 case CrossfadeClass:
172 return downcast<CSSCrossfadeValue>(*this).isFixedSize();
173 case FilterImageClass:
174 return downcast<CSSFilterImageValue>(*this).isFixedSize();
175 case LinearGradientClass:
176 return downcast<CSSLinearGradientValue>(*this).isFixedSize();
177 case RadialGradientClass:
178 return downcast<CSSRadialGradientValue>(*this).isFixedSize();
179 case ConicGradientClass:
180 return downcast<CSSConicGradientValue>(*this).isFixedSize();
181#if ENABLE(CSS_PAINTING_API)
182 case PaintImageClass:
183 return downcast<CSSPaintImageValue>(*this).isFixedSize();
184#endif
185 default:
186 ASSERT_NOT_REACHED();
187 }
188 return false;
189}
190
191FloatSize CSSImageGeneratorValue::fixedSize(const RenderElement& renderer)
192{
193 switch (classType()) {
194 case CanvasClass:
195 return downcast<CSSCanvasValue>(*this).fixedSize(renderer);
196 case CrossfadeClass:
197 return downcast<CSSCrossfadeValue>(*this).fixedSize(renderer);
198 case FilterImageClass:
199 return downcast<CSSFilterImageValue>(*this).fixedSize(renderer);
200 case LinearGradientClass:
201 return downcast<CSSLinearGradientValue>(*this).fixedSize(renderer);
202 case RadialGradientClass:
203 return downcast<CSSRadialGradientValue>(*this).fixedSize(renderer);
204 case ConicGradientClass:
205 return downcast<CSSConicGradientValue>(*this).fixedSize(renderer);
206#if ENABLE(CSS_PAINTING_API)
207 case PaintImageClass:
208 return downcast<CSSPaintImageValue>(*this).fixedSize(renderer);
209#endif
210 default:
211 ASSERT_NOT_REACHED();
212 }
213 return FloatSize();
214}
215
216bool CSSImageGeneratorValue::isPending() const
217{
218 switch (classType()) {
219 case CrossfadeClass:
220 return downcast<CSSCrossfadeValue>(*this).isPending();
221 case CanvasClass:
222 return downcast<CSSCanvasValue>(*this).isPending();
223 case NamedImageClass:
224 return downcast<CSSNamedImageValue>(*this).isPending();
225 case FilterImageClass:
226 return downcast<CSSFilterImageValue>(*this).isPending();
227 case LinearGradientClass:
228 return downcast<CSSLinearGradientValue>(*this).isPending();
229 case RadialGradientClass:
230 return downcast<CSSRadialGradientValue>(*this).isPending();
231 case ConicGradientClass:
232 return downcast<CSSConicGradientValue>(*this).isPending();
233#if ENABLE(CSS_PAINTING_API)
234 case PaintImageClass:
235 return downcast<CSSPaintImageValue>(*this).isPending();
236#endif
237 default:
238 ASSERT_NOT_REACHED();
239 }
240 return false;
241}
242
243bool CSSImageGeneratorValue::knownToBeOpaque(const RenderElement& renderer) const
244{
245 switch (classType()) {
246 case CrossfadeClass:
247 return downcast<CSSCrossfadeValue>(*this).knownToBeOpaque(renderer);
248 case CanvasClass:
249 return false;
250 case NamedImageClass:
251 return false;
252 case FilterImageClass:
253 return downcast<CSSFilterImageValue>(*this).knownToBeOpaque(renderer);
254 case LinearGradientClass:
255 return downcast<CSSLinearGradientValue>(*this).knownToBeOpaque(renderer);
256 case RadialGradientClass:
257 return downcast<CSSRadialGradientValue>(*this).knownToBeOpaque(renderer);
258 case ConicGradientClass:
259 return downcast<CSSConicGradientValue>(*this).knownToBeOpaque(renderer);
260#if ENABLE(CSS_PAINTING_API)
261 case PaintImageClass:
262 return downcast<CSSPaintImageValue>(*this).knownToBeOpaque(renderer);
263#endif
264 default:
265 ASSERT_NOT_REACHED();
266 }
267 return false;
268}
269
270void CSSImageGeneratorValue::loadSubimages(CachedResourceLoader& cachedResourceLoader, const ResourceLoaderOptions& options)
271{
272 switch (classType()) {
273 case CrossfadeClass:
274 downcast<CSSCrossfadeValue>(*this).loadSubimages(cachedResourceLoader, options);
275 break;
276 case CanvasClass:
277 downcast<CSSCanvasValue>(*this).loadSubimages(cachedResourceLoader, options);
278 break;
279 case FilterImageClass:
280 downcast<CSSFilterImageValue>(*this).loadSubimages(cachedResourceLoader, options);
281 break;
282 case LinearGradientClass:
283 downcast<CSSLinearGradientValue>(*this).loadSubimages(cachedResourceLoader, options);
284 break;
285 case RadialGradientClass:
286 downcast<CSSRadialGradientValue>(*this).loadSubimages(cachedResourceLoader, options);
287 break;
288 case ConicGradientClass:
289 downcast<CSSConicGradientValue>(*this).loadSubimages(cachedResourceLoader, options);
290 break;
291#if ENABLE(CSS_PAINTING_API)
292 case PaintImageClass:
293 downcast<CSSPaintImageValue>(*this).loadSubimages(cachedResourceLoader, options);
294 break;
295#endif
296 default:
297 ASSERT_NOT_REACHED();
298 }
299}
300
301bool CSSImageGeneratorValue::operator==(const CSSImageGeneratorValue& other) const
302{
303 if (classType() != other.classType())
304 return false;
305
306 switch (classType()) {
307 case CrossfadeClass:
308 return downcast<CSSCrossfadeValue>(*this).equals(downcast<CSSCrossfadeValue>(other));
309 case CanvasClass:
310 return downcast<CSSCanvasValue>(*this).equals(downcast<CSSCanvasValue>(other));
311 case FilterImageClass:
312 return downcast<CSSFilterImageValue>(*this).equals(downcast<CSSFilterImageValue>(other));
313 case LinearGradientClass:
314 return downcast<CSSLinearGradientValue>(*this).equals(downcast<CSSLinearGradientValue>(other));
315 case RadialGradientClass:
316 return downcast<CSSRadialGradientValue>(*this).equals(downcast<CSSRadialGradientValue>(other));
317 case ConicGradientClass:
318 return downcast<CSSConicGradientValue>(*this).equals(downcast<CSSConicGradientValue>(other));
319#if ENABLE(CSS_PAINTING_API)
320 case PaintImageClass:
321 return downcast<CSSPaintImageValue>(*this).equals(downcast<CSSPaintImageValue>(other));
322#endif
323 default:
324 ASSERT_NOT_REACHED();
325 }
326
327 return false;
328}
329
330bool CSSImageGeneratorValue::subimageIsPending(const CSSValue& value)
331{
332 if (is<CSSImageValue>(value))
333 return downcast<CSSImageValue>(value).isPending();
334
335 if (is<CSSImageGeneratorValue>(value))
336 return downcast<CSSImageGeneratorValue>(value).isPending();
337
338 if (is<CSSPrimitiveValue>(value) && downcast<CSSPrimitiveValue>(value).valueID() == CSSValueNone)
339 return false;
340
341 ASSERT_NOT_REACHED();
342 return false;
343}
344
345CachedImage* CSSImageGeneratorValue::cachedImageForCSSValue(CSSValue& value, CachedResourceLoader& cachedResourceLoader, const ResourceLoaderOptions& options)
346{
347 if (is<CSSImageValue>(value)) {
348 auto& imageValue = downcast<CSSImageValue>(value);
349 return imageValue.loadImage(cachedResourceLoader, options);
350 }
351
352 if (is<CSSImageGeneratorValue>(value)) {
353 downcast<CSSImageGeneratorValue>(value).loadSubimages(cachedResourceLoader, options);
354 // FIXME: Handle CSSImageGeneratorValue (and thus cross-fades with gradients and canvas).
355 return nullptr;
356 }
357
358 if (is<CSSPrimitiveValue>(value) && downcast<CSSPrimitiveValue>(value).valueID() == CSSValueNone)
359 return nullptr;
360
361 ASSERT_NOT_REACHED();
362 return nullptr;
363}
364
365} // namespace WebCore
Note: See TracBrowser for help on using the repository browser.