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