1/*
2 * Copyright (C) 2011 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 "CSSCrossfadeValue.h"
29
30#include "AnimationUtilities.h"
31#include "CSSImageValue.h"
32#include "CachedImage.h"
33#include "CachedResourceLoader.h"
34#include "CrossfadeGeneratedImage.h"
35#include "RenderElement.h"
36#include "StyleCachedImage.h"
37#include <wtf/text/StringBuilder.h>
38
39namespace WebCore {
40
41static inline double blendFunc(double from, double to, double progress)
42{
43 return blend(from, to, progress);
44}
45
46static bool subimageKnownToBeOpaque(const CSSValue& value, const RenderElement& renderer)
47{
48 if (is<CSSImageValue>(value))
49 return downcast<CSSImageValue>(value).knownToBeOpaque(renderer);
50
51 if (is<CSSImageGeneratorValue>(value))
52 return downcast<CSSImageGeneratorValue>(value).knownToBeOpaque(renderer);
53
54 ASSERT_NOT_REACHED();
55
56 return false;
57}
58
59inline CSSCrossfadeValue::SubimageObserver::SubimageObserver(CSSCrossfadeValue& owner)
60 : m_owner(owner)
61{
62}
63
64void CSSCrossfadeValue::SubimageObserver::imageChanged(CachedImage*, const IntRect*)
65{
66 m_owner.crossfadeChanged();
67}
68
69inline CSSCrossfadeValue::CSSCrossfadeValue(Ref<CSSValue>&& fromValue, Ref<CSSValue>&& toValue, Ref<CSSPrimitiveValue>&& percentageValue, bool prefixed)
70 : CSSImageGeneratorValue(CrossfadeClass)
71 , m_fromValue(WTFMove(fromValue))
72 , m_toValue(WTFMove(toValue))
73 , m_percentageValue(WTFMove(percentageValue))
74 , m_subimageObserver(*this)
75 , m_isPrefixed(prefixed)
76{
77}
78
79Ref<CSSCrossfadeValue> CSSCrossfadeValue::create(Ref<CSSValue>&& fromValue, Ref<CSSValue>&& toValue, Ref<CSSPrimitiveValue>&& percentageValue, bool prefixed)
80{
81 return adoptRef(*new CSSCrossfadeValue(WTFMove(fromValue), WTFMove(toValue), WTFMove(percentageValue), prefixed));
82}
83
84CSSCrossfadeValue::~CSSCrossfadeValue()
85{
86 if (m_cachedFromImage)
87 m_cachedFromImage->removeClient(m_subimageObserver);
88 if (m_cachedToImage)
89 m_cachedToImage->removeClient(m_subimageObserver);
90}
91
92String CSSCrossfadeValue::customCSSText() const
93{
94 StringBuilder result;
95 if (m_isPrefixed)
96 result.appendLiteral("-webkit-cross-fade(");
97 else
98 result.appendLiteral("cross-fade(");
99 result.append(m_fromValue->cssText());
100 result.appendLiteral(", ");
101 result.append(m_toValue->cssText());
102 result.appendLiteral(", ");
103 result.append(m_percentageValue->cssText());
104 result.append(')');
105 return result.toString();
106}
107
108FloatSize CSSCrossfadeValue::fixedSize(const RenderElement& renderer)
109{
110 float percentage = m_percentageValue->floatValue();
111 float inversePercentage = 1 - percentage;
112
113 // FIXME: Skip Content Security Policy check when cross fade is applied to an element in a user agent shadow tree.
114 // See <https://bugs.webkit.org/show_bug.cgi?id=146663>.
115 auto options = CachedResourceLoader::defaultCachedResourceOptions();
116
117 auto& cachedResourceLoader = renderer.document().cachedResourceLoader();
118 auto* cachedFromImage = cachedImageForCSSValue(m_fromValue, cachedResourceLoader, options);
119 auto* cachedToImage = cachedImageForCSSValue(m_toValue, cachedResourceLoader, options);
120
121 if (!cachedFromImage || !cachedToImage)
122 return FloatSize();
123
124 FloatSize fromImageSize = cachedFromImage->imageForRenderer(&renderer)->size();
125 FloatSize toImageSize = cachedToImage->imageForRenderer(&renderer)->size();
126
127 // Rounding issues can cause transitions between images of equal size to return
128 // a different fixed size; avoid performing the interpolation if the images are the same size.
129 if (fromImageSize == toImageSize)
130 return fromImageSize;
131
132 return fromImageSize * inversePercentage + toImageSize * percentage;
133}
134
135bool CSSCrossfadeValue::isPending() const
136{
137 return CSSImageGeneratorValue::subimageIsPending(m_fromValue)
138 || CSSImageGeneratorValue::subimageIsPending(m_toValue);
139}
140
141bool CSSCrossfadeValue::knownToBeOpaque(const RenderElement& renderer) const
142{
143 return subimageKnownToBeOpaque(m_fromValue, renderer)
144 && subimageKnownToBeOpaque(m_toValue, renderer);
145}
146
147void CSSCrossfadeValue::loadSubimages(CachedResourceLoader& cachedResourceLoader, const ResourceLoaderOptions& options)
148{
149 auto oldCachedFromImage = m_cachedFromImage;
150 auto oldCachedToImage = m_cachedToImage;
151
152 m_cachedFromImage = CSSImageGeneratorValue::cachedImageForCSSValue(m_fromValue, cachedResourceLoader, options);
153 m_cachedToImage = CSSImageGeneratorValue::cachedImageForCSSValue(m_toValue, cachedResourceLoader, options);
154
155 if (m_cachedFromImage != oldCachedFromImage) {
156 if (oldCachedFromImage)
157 oldCachedFromImage->removeClient(m_subimageObserver);
158 if (m_cachedFromImage)
159 m_cachedFromImage->addClient(m_subimageObserver);
160 }
161
162 if (m_cachedToImage != oldCachedToImage) {
163 if (oldCachedToImage)
164 oldCachedToImage->removeClient(m_subimageObserver);
165 if (m_cachedToImage)
166 m_cachedToImage->addClient(m_subimageObserver);
167 }
168
169 // FIXME: Unclear why this boolean adds any value; for now keeping it around to avoid changing semantics.
170 m_subimagesAreReady = true;
171}
172
173Image* CSSCrossfadeValue::image(RenderElement& renderer, const FloatSize& size)
174{
175 if (size.isEmpty())
176 return nullptr;
177
178 // FIXME: Skip Content Security Policy check when cross fade is applied to an element in a user agent shadow tree.
179 // See <https://bugs.webkit.org/show_bug.cgi?id=146663>.
180 auto options = CachedResourceLoader::defaultCachedResourceOptions();
181
182 auto& cachedResourceLoader = renderer.document().cachedResourceLoader();
183 auto* cachedFromImage = cachedImageForCSSValue(m_fromValue, cachedResourceLoader, options);
184 auto* cachedToImage = cachedImageForCSSValue(m_toValue, cachedResourceLoader, options);
185
186 if (!cachedFromImage || !cachedToImage)
187 return &Image::nullImage();
188
189 auto* fromImage = cachedFromImage->imageForRenderer(&renderer);
190 auto* toImage = cachedToImage->imageForRenderer(&renderer);
191
192 if (!fromImage || !toImage)
193 return &Image::nullImage();
194
195 m_generatedImage = CrossfadeGeneratedImage::create(*fromImage, *toImage, m_percentageValue->floatValue(), fixedSize(renderer), size);
196 return m_generatedImage.get();
197}
198
199inline void CSSCrossfadeValue::crossfadeChanged()
200{
201 if (!m_subimagesAreReady)
202 return;
203 for (auto& client : clients())
204 client.key->imageChanged(this);
205}
206
207bool CSSCrossfadeValue::traverseSubresources(const WTF::Function<bool (const CachedResource&)>& handler) const
208{
209 if (m_cachedFromImage && handler(*m_cachedFromImage))
210 return true;
211 if (m_cachedToImage && handler(*m_cachedToImage))
212 return true;
213 return false;
214}
215
216RefPtr<CSSCrossfadeValue> CSSCrossfadeValue::blend(const CSSCrossfadeValue& from, double progress) const
217{
218 ASSERT(equalInputImages(from));
219
220 if (!m_cachedToImage || !m_cachedFromImage)
221 return nullptr;
222
223 auto fromImageValue = CSSImageValue::create(*m_cachedFromImage);
224 auto toImageValue = CSSImageValue::create(*m_cachedToImage);
225
226 double fromPercentage = from.m_percentageValue->doubleValue();
227 if (from.m_percentageValue->isPercentage())
228 fromPercentage /= 100.0;
229 double toPercentage = m_percentageValue->doubleValue();
230 if (m_percentageValue->isPercentage())
231 toPercentage /= 100.0;
232 auto percentageValue = CSSPrimitiveValue::create(blendFunc(fromPercentage, toPercentage, progress), CSSPrimitiveValue::CSS_NUMBER);
233
234 return CSSCrossfadeValue::create(WTFMove(fromImageValue), WTFMove(toImageValue), WTFMove(percentageValue), from.isPrefixed() && isPrefixed());
235}
236
237bool CSSCrossfadeValue::equals(const CSSCrossfadeValue& other) const
238{
239 return equalInputImages(other) && compareCSSValue(m_percentageValue, other.m_percentageValue);
240}
241
242
243bool CSSCrossfadeValue::equalInputImages(const CSSCrossfadeValue& other) const
244{
245 return compareCSSValue(m_fromValue, other.m_fromValue) && compareCSSValue(m_toValue, other.m_toValue);
246}
247
248} // namespace WebCore
249