• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4 
5 #include "tests/cefclient/browser/image_cache.h"
6 
7 #include <algorithm>
8 
9 #include "tests/shared/browser/file_util.h"
10 #include "tests/shared/browser/resource_util.h"
11 
12 namespace client {
13 
14 namespace {
15 
16 const char kEmptyId[] = "__empty";
17 
18 }  // namespace
19 
ImageCache()20 ImageCache::ImageCache() {}
21 
~ImageCache()22 ImageCache::~ImageCache() {
23   CEF_REQUIRE_UI_THREAD();
24 }
25 
ImageRep(const std::string & path,float scale_factor)26 ImageCache::ImageRep::ImageRep(const std::string& path, float scale_factor)
27     : path_(path), scale_factor_(scale_factor) {
28   DCHECK(!path_.empty());
29   DCHECK_GT(scale_factor_, 0.0f);
30 }
31 
ImageInfo(const std::string & id,const ImageRepSet & reps,bool internal,bool force_reload)32 ImageCache::ImageInfo::ImageInfo(const std::string& id,
33                                  const ImageRepSet& reps,
34                                  bool internal,
35                                  bool force_reload)
36     : id_(id), reps_(reps), internal_(internal), force_reload_(force_reload) {
37 #ifndef NDEBUG
38   DCHECK(!id_.empty());
39   if (id_ != kEmptyId)
40     DCHECK(!reps_.empty());
41 #endif
42 }
43 
44 // static
Empty()45 ImageCache::ImageInfo ImageCache::ImageInfo::Empty() {
46   return ImageInfo(kEmptyId, ImageRepSet(), true, false);
47 }
48 
49 // static
Create1x(const std::string & id,const std::string & path_1x,bool internal)50 ImageCache::ImageInfo ImageCache::ImageInfo::Create1x(
51     const std::string& id,
52     const std::string& path_1x,
53     bool internal) {
54   ImageRepSet reps;
55   reps.push_back(ImageRep(path_1x, 1.0f));
56   return ImageInfo(id, reps, internal, false);
57 }
58 
59 // static
Create2x(const std::string & id,const std::string & path_1x,const std::string & path_2x,bool internal)60 ImageCache::ImageInfo ImageCache::ImageInfo::Create2x(
61     const std::string& id,
62     const std::string& path_1x,
63     const std::string& path_2x,
64     bool internal) {
65   ImageRepSet reps;
66   reps.push_back(ImageRep(path_1x, 1.0f));
67   reps.push_back(ImageRep(path_2x, 2.0f));
68   return ImageInfo(id, reps, internal, false);
69 }
70 
71 // static
Create2x(const std::string & id)72 ImageCache::ImageInfo ImageCache::ImageInfo::Create2x(const std::string& id) {
73   return Create2x(id, id + ".1x.png", id + ".2x.png", true);
74 }
75 
76 struct ImageCache::ImageContent {
ImageContentclient::ImageCache::ImageContent77   ImageContent() {}
78 
79   struct RepContent {
RepContentclient::ImageCache::ImageContent::RepContent80     RepContent(ImageType type, float scale_factor, const std::string& contents)
81         : type_(type), scale_factor_(scale_factor), contents_(contents) {}
82 
83     ImageType type_;
84     float scale_factor_;
85     std::string contents_;
86   };
87   typedef std::vector<RepContent> RepContentSet;
88   RepContentSet contents_;
89 
90   CefRefPtr<CefImage> image_;
91 };
92 
LoadImages(const ImageInfoSet & image_info,LoadImagesCallback callback)93 void ImageCache::LoadImages(const ImageInfoSet& image_info,
94                             LoadImagesCallback callback) {
95   DCHECK(!image_info.empty());
96   DCHECK(!callback.is_null());
97 
98   if (!CefCurrentlyOn(TID_UI)) {
99     CefPostTask(TID_UI, base::BindOnce(&ImageCache::LoadImages, this,
100                                        image_info, std::move(callback)));
101     return;
102   }
103 
104   ImageSet images;
105   bool missing_images = false;
106 
107   ImageInfoSet::const_iterator it = image_info.begin();
108   for (; it != image_info.end(); ++it) {
109     const ImageInfo& info = *it;
110 
111     if (info.id_ == kEmptyId) {
112       // Image intentionally left empty.
113       images.push_back(nullptr);
114       continue;
115     }
116 
117     ImageMap::iterator it2 = image_map_.find(info.id_);
118     if (it2 != image_map_.end()) {
119       if (!info.force_reload_) {
120         // Image already exists.
121         images.push_back(it2->second);
122         continue;
123       }
124 
125       // Remove the existing image from the map.
126       image_map_.erase(it2);
127     }
128 
129     // Load the image.
130     images.push_back(nullptr);
131     if (!missing_images)
132       missing_images = true;
133   }
134 
135   if (missing_images) {
136     CefPostTask(TID_FILE_USER_BLOCKING,
137                 base::BindOnce(&ImageCache::LoadMissing, this, image_info,
138                                images, std::move(callback)));
139   } else {
140     std::move(callback).Run(images);
141   }
142 }
143 
GetCachedImage(const std::string & image_id)144 CefRefPtr<CefImage> ImageCache::GetCachedImage(const std::string& image_id) {
145   CEF_REQUIRE_UI_THREAD();
146   DCHECK(!image_id.empty());
147 
148   ImageMap::const_iterator it = image_map_.find(image_id);
149   if (it != image_map_.end())
150     return it->second;
151 
152   return nullptr;
153 }
154 
155 // static
GetImageType(const std::string & path)156 ImageCache::ImageType ImageCache::GetImageType(const std::string& path) {
157   std::string ext = file_util::GetFileExtension(path);
158   if (ext.empty())
159     return TYPE_NONE;
160 
161   std::transform(ext.begin(), ext.end(), ext.begin(), tolower);
162   if (ext == "png")
163     return TYPE_PNG;
164   if (ext == "jpg" || ext == "jpeg")
165     return TYPE_JPEG;
166 
167   return TYPE_NONE;
168 }
169 
LoadMissing(const ImageInfoSet & image_info,const ImageSet & images,LoadImagesCallback callback)170 void ImageCache::LoadMissing(const ImageInfoSet& image_info,
171                              const ImageSet& images,
172                              LoadImagesCallback callback) {
173   CEF_REQUIRE_FILE_USER_BLOCKING_THREAD();
174 
175   DCHECK_EQ(image_info.size(), images.size());
176 
177   ImageContentSet contents;
178 
179   ImageInfoSet::const_iterator it1 = image_info.begin();
180   ImageSet::const_iterator it2 = images.begin();
181   for (; it1 != image_info.end() && it2 != images.end(); ++it1, ++it2) {
182     const ImageInfo& info = *it1;
183     ImageContent content;
184     if (*it2 || info.id_ == kEmptyId) {
185       // Image already exists or is intentionally empty.
186       content.image_ = *it2;
187     } else {
188       LoadImageContents(info, &content);
189     }
190     contents.push_back(content);
191   }
192 
193   CefPostTask(TID_UI, base::BindOnce(&ImageCache::UpdateCache, this, image_info,
194                                      contents, std::move(callback)));
195 }
196 
197 // static
LoadImageContents(const ImageInfo & info,ImageContent * content)198 bool ImageCache::LoadImageContents(const ImageInfo& info,
199                                    ImageContent* content) {
200   CEF_REQUIRE_FILE_USER_BLOCKING_THREAD();
201 
202   ImageRepSet::const_iterator it = info.reps_.begin();
203   for (; it != info.reps_.end(); ++it) {
204     const ImageRep& rep = *it;
205     ImageType rep_type;
206     std::string rep_contents;
207     if (!LoadImageContents(rep.path_, info.internal_, &rep_type,
208                            &rep_contents)) {
209       LOG(ERROR) << "Failed to load image " << info.id_ << " from path "
210                  << rep.path_;
211       return false;
212     }
213     content->contents_.push_back(
214         ImageContent::RepContent(rep_type, rep.scale_factor_, rep_contents));
215   }
216 
217   return true;
218 }
219 
220 // static
LoadImageContents(const std::string & path,bool internal,ImageType * type,std::string * contents)221 bool ImageCache::LoadImageContents(const std::string& path,
222                                    bool internal,
223                                    ImageType* type,
224                                    std::string* contents) {
225   CEF_REQUIRE_FILE_USER_BLOCKING_THREAD();
226 
227   *type = GetImageType(path);
228   if (*type == TYPE_NONE)
229     return false;
230 
231   if (internal) {
232     if (!LoadBinaryResource(path.c_str(), *contents))
233       return false;
234   } else if (!file_util::ReadFileToString(path, contents)) {
235     return false;
236   }
237 
238   return !contents->empty();
239 }
240 
UpdateCache(const ImageInfoSet & image_info,const ImageContentSet & contents,LoadImagesCallback callback)241 void ImageCache::UpdateCache(const ImageInfoSet& image_info,
242                              const ImageContentSet& contents,
243                              LoadImagesCallback callback) {
244   CEF_REQUIRE_UI_THREAD();
245 
246   DCHECK_EQ(image_info.size(), contents.size());
247 
248   ImageSet images;
249 
250   ImageInfoSet::const_iterator it1 = image_info.begin();
251   ImageContentSet::const_iterator it2 = contents.begin();
252   for (; it1 != image_info.end() && it2 != contents.end(); ++it1, ++it2) {
253     const ImageInfo& info = *it1;
254     const ImageContent& content = *it2;
255     if (content.image_ || info.id_ == kEmptyId) {
256       // Image already exists or is intentionally empty.
257       images.push_back(content.image_);
258     } else {
259       CefRefPtr<CefImage> image = CreateImage(info.id_, content);
260       images.push_back(image);
261 
262       // Add the image to the map.
263       image_map_.insert(std::make_pair(info.id_, image));
264     }
265   }
266 
267   std::move(callback).Run(images);
268 }
269 
270 // static
CreateImage(const std::string & image_id,const ImageContent & content)271 CefRefPtr<CefImage> ImageCache::CreateImage(const std::string& image_id,
272                                             const ImageContent& content) {
273   CEF_REQUIRE_UI_THREAD();
274 
275   // Shouldn't be creating an image if one already exists.
276   DCHECK(!content.image_);
277 
278   if (content.contents_.empty())
279     return nullptr;
280 
281   CefRefPtr<CefImage> image = CefImage::CreateImage();
282 
283   ImageContent::RepContentSet::const_iterator it = content.contents_.begin();
284   for (; it != content.contents_.end(); ++it) {
285     const ImageContent::RepContent& rep = *it;
286     if (rep.type_ == TYPE_PNG) {
287       if (!image->AddPNG(rep.scale_factor_, rep.contents_.c_str(),
288                          rep.contents_.size())) {
289         LOG(ERROR) << "Failed to create image " << image_id << " for PNG@"
290                    << rep.scale_factor_;
291         return nullptr;
292       }
293     } else if (rep.type_ == TYPE_JPEG) {
294       if (!image->AddJPEG(rep.scale_factor_, rep.contents_.c_str(),
295                           rep.contents_.size())) {
296         LOG(ERROR) << "Failed to create image " << image_id << " for JPG@"
297                    << rep.scale_factor_;
298         return nullptr;
299       }
300     } else {
301       NOTREACHED();
302       return nullptr;
303     }
304   }
305 
306   return image;
307 }
308 
309 }  // namespace client
310