2017-08-04 00:55:19 +02:00
|
|
|
// Copyright (c) 2017 The Chromium Embedded Framework Authors. All rights
|
|
|
|
// reserved. Use of this source code is governed by a BSD-style license that
|
|
|
|
// can be found in the LICENSE file.
|
|
|
|
|
|
|
|
#include "tests/cefclient/browser/image_cache.h"
|
|
|
|
|
2021-06-20 18:08:10 +02:00
|
|
|
#include <algorithm>
|
|
|
|
|
2017-08-04 00:55:19 +02:00
|
|
|
#include "tests/shared/browser/file_util.h"
|
|
|
|
#include "tests/shared/browser/resource_util.h"
|
2022-08-02 22:30:37 +02:00
|
|
|
#include "tests/shared/common/string_util.h"
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
namespace client {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
const char kEmptyId[] = "__empty";
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
2024-01-20 03:22:56 +01:00
|
|
|
ImageCache::ImageCache() = default;
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
ImageCache::~ImageCache() {
|
|
|
|
CEF_REQUIRE_UI_THREAD();
|
|
|
|
}
|
|
|
|
|
|
|
|
ImageCache::ImageRep::ImageRep(const std::string& path, float scale_factor)
|
|
|
|
: path_(path), scale_factor_(scale_factor) {
|
|
|
|
DCHECK(!path_.empty());
|
|
|
|
DCHECK_GT(scale_factor_, 0.0f);
|
|
|
|
}
|
|
|
|
|
|
|
|
ImageCache::ImageInfo::ImageInfo(const std::string& id,
|
|
|
|
const ImageRepSet& reps,
|
|
|
|
bool internal,
|
|
|
|
bool force_reload)
|
|
|
|
: id_(id), reps_(reps), internal_(internal), force_reload_(force_reload) {
|
|
|
|
#ifndef NDEBUG
|
|
|
|
DCHECK(!id_.empty());
|
2023-01-02 23:59:03 +01:00
|
|
|
if (id_ != kEmptyId) {
|
2017-08-04 00:55:19 +02:00
|
|
|
DCHECK(!reps_.empty());
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-08-04 00:55:19 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ImageCache::ImageInfo ImageCache::ImageInfo::Empty() {
|
|
|
|
return ImageInfo(kEmptyId, ImageRepSet(), true, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ImageCache::ImageInfo ImageCache::ImageInfo::Create1x(
|
|
|
|
const std::string& id,
|
|
|
|
const std::string& path_1x,
|
|
|
|
bool internal) {
|
|
|
|
ImageRepSet reps;
|
2024-01-20 03:22:56 +01:00
|
|
|
reps.emplace_back(path_1x, 1.0f);
|
2017-08-04 00:55:19 +02:00
|
|
|
return ImageInfo(id, reps, internal, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ImageCache::ImageInfo ImageCache::ImageInfo::Create2x(
|
|
|
|
const std::string& id,
|
|
|
|
const std::string& path_1x,
|
|
|
|
const std::string& path_2x,
|
|
|
|
bool internal) {
|
|
|
|
ImageRepSet reps;
|
2024-01-20 03:22:56 +01:00
|
|
|
reps.emplace_back(path_1x, 1.0f);
|
|
|
|
reps.emplace_back(path_2x, 2.0f);
|
2017-08-04 00:55:19 +02:00
|
|
|
return ImageInfo(id, reps, internal, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ImageCache::ImageInfo ImageCache::ImageInfo::Create2x(const std::string& id) {
|
|
|
|
return Create2x(id, id + ".1x.png", id + ".2x.png", true);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ImageCache::ImageContent {
|
2024-01-20 03:22:56 +01:00
|
|
|
ImageContent() = default;
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
struct RepContent {
|
|
|
|
RepContent(ImageType type, float scale_factor, const std::string& contents)
|
|
|
|
: type_(type), scale_factor_(scale_factor), contents_(contents) {}
|
|
|
|
|
|
|
|
ImageType type_;
|
|
|
|
float scale_factor_;
|
|
|
|
std::string contents_;
|
|
|
|
};
|
|
|
|
typedef std::vector<RepContent> RepContentSet;
|
|
|
|
RepContentSet contents_;
|
|
|
|
|
|
|
|
CefRefPtr<CefImage> image_;
|
|
|
|
};
|
|
|
|
|
|
|
|
void ImageCache::LoadImages(const ImageInfoSet& image_info,
|
2021-06-19 21:54:45 +02:00
|
|
|
LoadImagesCallback callback) {
|
2017-08-04 00:55:19 +02:00
|
|
|
DCHECK(!image_info.empty());
|
|
|
|
DCHECK(!callback.is_null());
|
|
|
|
|
|
|
|
if (!CefCurrentlyOn(TID_UI)) {
|
2021-06-19 21:54:45 +02:00
|
|
|
CefPostTask(TID_UI, base::BindOnce(&ImageCache::LoadImages, this,
|
|
|
|
image_info, std::move(callback)));
|
2017-08-04 00:55:19 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ImageSet images;
|
|
|
|
bool missing_images = false;
|
|
|
|
|
|
|
|
ImageInfoSet::const_iterator it = image_info.begin();
|
|
|
|
for (; it != image_info.end(); ++it) {
|
|
|
|
const ImageInfo& info = *it;
|
|
|
|
|
|
|
|
if (info.id_ == kEmptyId) {
|
|
|
|
// Image intentionally left empty.
|
2020-01-15 15:28:12 +01:00
|
|
|
images.push_back(nullptr);
|
2017-08-04 00:55:19 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ImageMap::iterator it2 = image_map_.find(info.id_);
|
|
|
|
if (it2 != image_map_.end()) {
|
|
|
|
if (!info.force_reload_) {
|
|
|
|
// Image already exists.
|
|
|
|
images.push_back(it2->second);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the existing image from the map.
|
|
|
|
image_map_.erase(it2);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load the image.
|
2020-01-15 15:28:12 +01:00
|
|
|
images.push_back(nullptr);
|
2023-01-02 23:59:03 +01:00
|
|
|
if (!missing_images) {
|
2017-08-04 00:55:19 +02:00
|
|
|
missing_images = true;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (missing_images) {
|
2021-04-15 04:29:30 +02:00
|
|
|
CefPostTask(TID_FILE_USER_BLOCKING,
|
2021-06-19 21:54:45 +02:00
|
|
|
base::BindOnce(&ImageCache::LoadMissing, this, image_info,
|
|
|
|
images, std::move(callback)));
|
2017-08-04 00:55:19 +02:00
|
|
|
} else {
|
2021-06-19 21:54:45 +02:00
|
|
|
std::move(callback).Run(images);
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CefRefPtr<CefImage> ImageCache::GetCachedImage(const std::string& image_id) {
|
|
|
|
CEF_REQUIRE_UI_THREAD();
|
|
|
|
DCHECK(!image_id.empty());
|
|
|
|
|
|
|
|
ImageMap::const_iterator it = image_map_.find(image_id);
|
2023-01-02 23:59:03 +01:00
|
|
|
if (it != image_map_.end()) {
|
2017-08-04 00:55:19 +02:00
|
|
|
return it->second;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-08-04 00:55:19 +02:00
|
|
|
|
2020-01-15 15:28:12 +01:00
|
|
|
return nullptr;
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
ImageCache::ImageType ImageCache::GetImageType(const std::string& path) {
|
|
|
|
std::string ext = file_util::GetFileExtension(path);
|
2023-01-02 23:59:03 +01:00
|
|
|
if (ext.empty()) {
|
2017-08-04 00:55:19 +02:00
|
|
|
return TYPE_NONE;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-08-04 00:55:19 +02:00
|
|
|
|
2022-08-02 22:30:37 +02:00
|
|
|
ext = AsciiStrToLower(ext);
|
2023-01-02 23:59:03 +01:00
|
|
|
if (ext == "png") {
|
2017-08-04 00:55:19 +02:00
|
|
|
return TYPE_PNG;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
|
|
|
if (ext == "jpg" || ext == "jpeg") {
|
2017-08-04 00:55:19 +02:00
|
|
|
return TYPE_JPEG;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
return TYPE_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ImageCache::LoadMissing(const ImageInfoSet& image_info,
|
|
|
|
const ImageSet& images,
|
2021-06-19 21:54:45 +02:00
|
|
|
LoadImagesCallback callback) {
|
2021-05-19 23:34:06 +02:00
|
|
|
CEF_REQUIRE_FILE_USER_BLOCKING_THREAD();
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
DCHECK_EQ(image_info.size(), images.size());
|
|
|
|
|
|
|
|
ImageContentSet contents;
|
|
|
|
|
|
|
|
ImageInfoSet::const_iterator it1 = image_info.begin();
|
|
|
|
ImageSet::const_iterator it2 = images.begin();
|
|
|
|
for (; it1 != image_info.end() && it2 != images.end(); ++it1, ++it2) {
|
|
|
|
const ImageInfo& info = *it1;
|
|
|
|
ImageContent content;
|
|
|
|
if (*it2 || info.id_ == kEmptyId) {
|
|
|
|
// Image already exists or is intentionally empty.
|
|
|
|
content.image_ = *it2;
|
|
|
|
} else {
|
|
|
|
LoadImageContents(info, &content);
|
|
|
|
}
|
|
|
|
contents.push_back(content);
|
|
|
|
}
|
|
|
|
|
2021-06-19 21:54:45 +02:00
|
|
|
CefPostTask(TID_UI, base::BindOnce(&ImageCache::UpdateCache, this, image_info,
|
|
|
|
contents, std::move(callback)));
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
bool ImageCache::LoadImageContents(const ImageInfo& info,
|
|
|
|
ImageContent* content) {
|
2021-05-19 23:34:06 +02:00
|
|
|
CEF_REQUIRE_FILE_USER_BLOCKING_THREAD();
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
ImageRepSet::const_iterator it = info.reps_.begin();
|
|
|
|
for (; it != info.reps_.end(); ++it) {
|
|
|
|
const ImageRep& rep = *it;
|
|
|
|
ImageType rep_type;
|
|
|
|
std::string rep_contents;
|
|
|
|
if (!LoadImageContents(rep.path_, info.internal_, &rep_type,
|
|
|
|
&rep_contents)) {
|
|
|
|
LOG(ERROR) << "Failed to load image " << info.id_ << " from path "
|
|
|
|
<< rep.path_;
|
|
|
|
return false;
|
|
|
|
}
|
2024-01-20 03:22:56 +01:00
|
|
|
content->contents_.emplace_back(rep_type, rep.scale_factor_, rep_contents);
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
bool ImageCache::LoadImageContents(const std::string& path,
|
|
|
|
bool internal,
|
|
|
|
ImageType* type,
|
|
|
|
std::string* contents) {
|
2021-05-19 23:34:06 +02:00
|
|
|
CEF_REQUIRE_FILE_USER_BLOCKING_THREAD();
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
*type = GetImageType(path);
|
2023-01-02 23:59:03 +01:00
|
|
|
if (*type == TYPE_NONE) {
|
2017-08-04 00:55:19 +02:00
|
|
|
return false;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
if (internal) {
|
2023-01-02 23:59:03 +01:00
|
|
|
if (!LoadBinaryResource(path.c_str(), *contents)) {
|
2017-08-04 00:55:19 +02:00
|
|
|
return false;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-08-04 00:55:19 +02:00
|
|
|
} else if (!file_util::ReadFileToString(path, contents)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !contents->empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ImageCache::UpdateCache(const ImageInfoSet& image_info,
|
|
|
|
const ImageContentSet& contents,
|
2021-06-19 21:54:45 +02:00
|
|
|
LoadImagesCallback callback) {
|
2017-08-04 00:55:19 +02:00
|
|
|
CEF_REQUIRE_UI_THREAD();
|
|
|
|
|
|
|
|
DCHECK_EQ(image_info.size(), contents.size());
|
|
|
|
|
|
|
|
ImageSet images;
|
|
|
|
|
|
|
|
ImageInfoSet::const_iterator it1 = image_info.begin();
|
|
|
|
ImageContentSet::const_iterator it2 = contents.begin();
|
|
|
|
for (; it1 != image_info.end() && it2 != contents.end(); ++it1, ++it2) {
|
|
|
|
const ImageInfo& info = *it1;
|
|
|
|
const ImageContent& content = *it2;
|
|
|
|
if (content.image_ || info.id_ == kEmptyId) {
|
|
|
|
// Image already exists or is intentionally empty.
|
|
|
|
images.push_back(content.image_);
|
|
|
|
} else {
|
|
|
|
CefRefPtr<CefImage> image = CreateImage(info.id_, content);
|
|
|
|
images.push_back(image);
|
|
|
|
|
|
|
|
// Add the image to the map.
|
|
|
|
image_map_.insert(std::make_pair(info.id_, image));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-19 21:54:45 +02:00
|
|
|
std::move(callback).Run(images);
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// static
|
|
|
|
CefRefPtr<CefImage> ImageCache::CreateImage(const std::string& image_id,
|
|
|
|
const ImageContent& content) {
|
|
|
|
CEF_REQUIRE_UI_THREAD();
|
|
|
|
|
|
|
|
// Shouldn't be creating an image if one already exists.
|
|
|
|
DCHECK(!content.image_);
|
|
|
|
|
2023-01-02 23:59:03 +01:00
|
|
|
if (content.contents_.empty()) {
|
2020-01-15 15:28:12 +01:00
|
|
|
return nullptr;
|
2023-01-02 23:59:03 +01:00
|
|
|
}
|
2017-08-04 00:55:19 +02:00
|
|
|
|
|
|
|
CefRefPtr<CefImage> image = CefImage::CreateImage();
|
|
|
|
|
|
|
|
ImageContent::RepContentSet::const_iterator it = content.contents_.begin();
|
|
|
|
for (; it != content.contents_.end(); ++it) {
|
|
|
|
const ImageContent::RepContent& rep = *it;
|
|
|
|
if (rep.type_ == TYPE_PNG) {
|
|
|
|
if (!image->AddPNG(rep.scale_factor_, rep.contents_.c_str(),
|
|
|
|
rep.contents_.size())) {
|
|
|
|
LOG(ERROR) << "Failed to create image " << image_id << " for PNG@"
|
|
|
|
<< rep.scale_factor_;
|
2020-01-15 15:28:12 +01:00
|
|
|
return nullptr;
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
} else if (rep.type_ == TYPE_JPEG) {
|
|
|
|
if (!image->AddJPEG(rep.scale_factor_, rep.contents_.c_str(),
|
|
|
|
rep.contents_.size())) {
|
|
|
|
LOG(ERROR) << "Failed to create image " << image_id << " for JPG@"
|
|
|
|
<< rep.scale_factor_;
|
2020-01-15 15:28:12 +01:00
|
|
|
return nullptr;
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
NOTREACHED();
|
2020-01-15 15:28:12 +01:00
|
|
|
return nullptr;
|
2017-08-04 00:55:19 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace client
|