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