1 /*
2  * Copyright (C) 2011 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include "third_party/blink/renderer/core/css/css_crossfade_value.h"
27 
28 #include "third_party/blink/renderer/core/css/css_image_value.h"
29 #include "third_party/blink/renderer/core/layout/layout_object.h"
30 #include "third_party/blink/renderer/core/style/style_fetched_image.h"
31 #include "third_party/blink/renderer/core/svg/graphics/svg_image_for_container.h"
32 #include "third_party/blink/renderer/platform/graphics/crossfade_generated_image.h"
33 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
34 
35 namespace blink {
36 namespace cssvalue {
37 
SubimageIsPending(const CSSValue & value)38 static bool SubimageIsPending(const CSSValue& value) {
39   if (auto* image_value = DynamicTo<CSSImageValue>(value))
40     return image_value->IsCachePending();
41 
42   if (auto* image_generator_value = DynamicTo<CSSImageGeneratorValue>(value))
43     return image_generator_value->IsPending();
44 
45   NOTREACHED();
46 
47   return false;
48 }
49 
SubimageKnownToBeOpaque(const CSSValue & value,const Document & document,const ComputedStyle & style)50 static bool SubimageKnownToBeOpaque(const CSSValue& value,
51                                     const Document& document,
52                                     const ComputedStyle& style) {
53   if (auto* image_value = DynamicTo<CSSImageValue>(value))
54     return image_value->KnownToBeOpaque(document, style);
55 
56   if (auto* img_generator_value = DynamicTo<CSSImageGeneratorValue>(value))
57     return img_generator_value->KnownToBeOpaque(document, style);
58 
59   NOTREACHED();
60 
61   return false;
62 }
63 
CachedImageForCSSValue(CSSValue * value,const Document & document)64 static ImageResourceContent* CachedImageForCSSValue(CSSValue* value,
65                                                     const Document& document) {
66   if (!value)
67     return nullptr;
68 
69   if (auto* image_value = DynamicTo<CSSImageValue>(value)) {
70     StyleImage* style_image_resource =
71         image_value->CacheImage(document, FetchParameters::kNone);
72     if (!style_image_resource)
73       return nullptr;
74 
75     return style_image_resource->CachedImage();
76   }
77 
78   if (auto* img_generator_value = DynamicTo<CSSImageGeneratorValue>(value)) {
79     img_generator_value->LoadSubimages(document);
80     // FIXME: Handle CSSImageGeneratorValue (and thus cross-fades with gradients
81     // and canvas).
82     return nullptr;
83   }
84 
85   NOTREACHED();
86 
87   return nullptr;
88 }
89 
RenderableImageForCSSValue(CSSValue * value,const Document & document)90 static Image* RenderableImageForCSSValue(CSSValue* value,
91                                          const Document& document) {
92   ImageResourceContent* cached_image = CachedImageForCSSValue(value, document);
93 
94   if (!cached_image || cached_image->ErrorOccurred() ||
95       cached_image->GetImage()->IsNull())
96     return nullptr;
97 
98   return cached_image->GetImage();
99 }
100 
UrlForCSSValue(const CSSValue & value)101 static KURL UrlForCSSValue(const CSSValue& value) {
102   auto* image_value = DynamicTo<CSSImageValue>(value);
103   if (!image_value)
104     return KURL();
105 
106   return KURL(image_value->Url());
107 }
108 
CSSCrossfadeValue(CSSValue * from_value,CSSValue * to_value,CSSPrimitiveValue * percentage_value)109 CSSCrossfadeValue::CSSCrossfadeValue(CSSValue* from_value,
110                                      CSSValue* to_value,
111                                      CSSPrimitiveValue* percentage_value)
112     : CSSImageGeneratorValue(kCrossfadeClass),
113       from_value_(from_value),
114       to_value_(to_value),
115       percentage_value_(percentage_value),
116       cached_from_image_(nullptr),
117       cached_to_image_(nullptr),
118       crossfade_subimage_observer_(this) {}
119 
120 CSSCrossfadeValue::~CSSCrossfadeValue() = default;
121 
Dispose()122 void CSSCrossfadeValue::Dispose() {
123   if (cached_from_image_) {
124     cached_from_image_->RemoveObserver(&crossfade_subimage_observer_);
125     cached_from_image_ = nullptr;
126   }
127   if (cached_to_image_) {
128     cached_to_image_->RemoveObserver(&crossfade_subimage_observer_);
129     cached_to_image_ = nullptr;
130   }
131 }
132 
CustomCSSText() const133 String CSSCrossfadeValue::CustomCSSText() const {
134   StringBuilder result;
135   result.Append("-webkit-cross-fade(");
136   result.Append(from_value_->CssText());
137   result.Append(", ");
138   result.Append(to_value_->CssText());
139   result.Append(", ");
140   result.Append(percentage_value_->CssText());
141   result.Append(')');
142   return result.ToString();
143 }
144 
ComputedCSSValue(const ComputedStyle & style,bool allow_visited_style)145 CSSCrossfadeValue* CSSCrossfadeValue::ComputedCSSValue(
146     const ComputedStyle& style,
147     bool allow_visited_style) {
148   CSSValue* from_value = from_value_;
149   if (auto* from_image_value = DynamicTo<CSSImageValue>(from_value_.Get())) {
150     from_value = from_image_value->ValueWithURLMadeAbsolute();
151   } else if (auto* from_generator_value =
152                  DynamicTo<CSSImageGeneratorValue>(from_value_.Get())) {
153     from_value =
154         from_generator_value->ComputedCSSValue(style, allow_visited_style);
155   }
156   CSSValue* to_value = to_value_;
157   if (auto* to_image_value = DynamicTo<CSSImageValue>(to_value_.Get())) {
158     to_value = to_image_value->ValueWithURLMadeAbsolute();
159   } else if (auto* to_generator_value =
160                  DynamicTo<CSSImageGeneratorValue>(to_value_.Get())) {
161     to_value = to_generator_value->ComputedCSSValue(style, allow_visited_style);
162   }
163   return MakeGarbageCollected<CSSCrossfadeValue>(from_value, to_value,
164                                                  percentage_value_);
165 }
166 
FixedSize(const Document & document,const FloatSize & default_object_size) const167 FloatSize CSSCrossfadeValue::FixedSize(
168     const Document& document,
169     const FloatSize& default_object_size) const {
170   Image* from_image = RenderableImageForCSSValue(from_value_.Get(), document);
171   Image* to_image = RenderableImageForCSSValue(to_value_.Get(), document);
172 
173   if (!from_image || !to_image)
174     return FloatSize();
175 
176   FloatSize from_image_size(from_image->Size());
177   FloatSize to_image_size(to_image->Size());
178 
179   if (auto* from_svg_image = DynamicTo<SVGImage>(from_image)) {
180     from_image_size = from_svg_image->ConcreteObjectSize(default_object_size);
181   }
182 
183   if (auto* to_svg_image = DynamicTo<SVGImage>(to_image)) {
184     to_image_size = to_svg_image->ConcreteObjectSize(default_object_size);
185   }
186 
187   // Rounding issues can cause transitions between images of equal size to
188   // return a different fixed size; avoid performing the interpolation if the
189   // images are the same size.
190   if (from_image_size == to_image_size)
191     return from_image_size;
192 
193   float percentage = percentage_value_->GetFloatValue();
194   float inverse_percentage = 1 - percentage;
195 
196   return FloatSize(from_image_size.Width() * inverse_percentage +
197                        to_image_size.Width() * percentage,
198                    from_image_size.Height() * inverse_percentage +
199                        to_image_size.Height() * percentage);
200 }
201 
IsPending() const202 bool CSSCrossfadeValue::IsPending() const {
203   return SubimageIsPending(*from_value_) || SubimageIsPending(*to_value_);
204 }
205 
KnownToBeOpaque(const Document & document,const ComputedStyle & style) const206 bool CSSCrossfadeValue::KnownToBeOpaque(const Document& document,
207                                         const ComputedStyle& style) const {
208   return SubimageKnownToBeOpaque(*from_value_, document, style) &&
209          SubimageKnownToBeOpaque(*to_value_, document, style);
210 }
211 
LoadSubimages(const Document & document)212 void CSSCrossfadeValue::LoadSubimages(const Document& document) {
213   ImageResourceContent* old_cached_from_image = cached_from_image_;
214   ImageResourceContent* old_cached_to_image = cached_to_image_;
215 
216   cached_from_image_ = CachedImageForCSSValue(from_value_.Get(), document);
217   cached_to_image_ = CachedImageForCSSValue(to_value_.Get(), document);
218 
219   if (cached_from_image_ != old_cached_from_image) {
220     if (old_cached_from_image)
221       old_cached_from_image->RemoveObserver(&crossfade_subimage_observer_);
222     if (cached_from_image_)
223       cached_from_image_->AddObserver(&crossfade_subimage_observer_);
224   }
225 
226   if (cached_to_image_ != old_cached_to_image) {
227     if (old_cached_to_image)
228       old_cached_to_image->RemoveObserver(&crossfade_subimage_observer_);
229     if (cached_to_image_)
230       cached_to_image_->AddObserver(&crossfade_subimage_observer_);
231   }
232 
233   crossfade_subimage_observer_.SetReady(true);
234 }
235 
GetImage(const ImageResourceObserver & client,const Document & document,const ComputedStyle &,const FloatSize & size) const236 scoped_refptr<Image> CSSCrossfadeValue::GetImage(
237     const ImageResourceObserver& client,
238     const Document& document,
239     const ComputedStyle&,
240     const FloatSize& size) const {
241   if (size.IsEmpty())
242     return nullptr;
243 
244   Image* from_image = RenderableImageForCSSValue(from_value_.Get(), document);
245   Image* to_image = RenderableImageForCSSValue(to_value_.Get(), document);
246 
247   if (!from_image || !to_image)
248     return Image::NullImage();
249 
250   scoped_refptr<Image> from_image_ref(from_image);
251   scoped_refptr<Image> to_image_ref(to_image);
252 
253   if (auto* from_svg_image = DynamicTo<SVGImage>(from_image)) {
254     from_image_ref = SVGImageForContainer::Create(from_svg_image, size, 1,
255                                                   UrlForCSSValue(*from_value_));
256   }
257 
258   if (auto* to_svg_image = DynamicTo<SVGImage>(to_image)) {
259     to_image_ref = SVGImageForContainer::Create(to_svg_image, size, 1,
260                                                 UrlForCSSValue(*to_value_));
261   }
262 
263   return CrossfadeGeneratedImage::Create(from_image_ref, to_image_ref,
264                                          percentage_value_->GetFloatValue(),
265                                          FixedSize(document, size), size);
266 }
267 
CrossfadeChanged(ImageResourceObserver::CanDeferInvalidation defer)268 void CSSCrossfadeValue::CrossfadeChanged(
269     ImageResourceObserver::CanDeferInvalidation defer) {
270   for (const auto& curr : Clients()) {
271     ImageResourceObserver* client =
272         const_cast<ImageResourceObserver*>(curr.key);
273     client->ImageChanged(static_cast<WrappedImagePtr>(this), defer);
274   }
275 }
276 
WillRenderImage() const277 bool CSSCrossfadeValue::WillRenderImage() const {
278   for (const auto& curr : Clients()) {
279     if (const_cast<ImageResourceObserver*>(curr.key)->WillRenderImage())
280       return true;
281   }
282   return false;
283 }
284 
ImageChanged(ImageResourceContent *,CanDeferInvalidation defer)285 void CSSCrossfadeValue::CrossfadeSubimageObserverProxy::ImageChanged(
286     ImageResourceContent*,
287     CanDeferInvalidation defer) {
288   if (ready_)
289     owner_value_->CrossfadeChanged(defer);
290 }
291 
WillRenderImage()292 bool CSSCrossfadeValue::CrossfadeSubimageObserverProxy::WillRenderImage() {
293   // If the images are not ready/loaded we won't paint them. If the images
294   // are ready then ask the clients.
295   return ready_ && owner_value_->WillRenderImage();
296 }
297 
HasFailedOrCanceledSubresources() const298 bool CSSCrossfadeValue::HasFailedOrCanceledSubresources() const {
299   if (cached_from_image_ && cached_from_image_->LoadFailedOrCanceled())
300     return true;
301   if (cached_to_image_ && cached_to_image_->LoadFailedOrCanceled())
302     return true;
303   return false;
304 }
305 
Equals(const CSSCrossfadeValue & other) const306 bool CSSCrossfadeValue::Equals(const CSSCrossfadeValue& other) const {
307   return DataEquivalent(from_value_, other.from_value_) &&
308          DataEquivalent(to_value_, other.to_value_) &&
309          DataEquivalent(percentage_value_, other.percentage_value_);
310 }
311 
TraceAfterDispatch(blink::Visitor * visitor) const312 void CSSCrossfadeValue::TraceAfterDispatch(blink::Visitor* visitor) const {
313   visitor->Trace(from_value_);
314   visitor->Trace(to_value_);
315   visitor->Trace(percentage_value_);
316   visitor->Trace(cached_from_image_);
317   visitor->Trace(cached_to_image_);
318   visitor->Trace(crossfade_subimage_observer_);
319   CSSImageGeneratorValue::TraceAfterDispatch(visitor);
320 }
321 
322 }  // namespace cssvalue
323 }  // namespace blink
324