#include "chrome/browser/apps/app_service/app_icon/app_icon_loader.h"
#include <memory>
#include <string_view>
#include <utility>
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/trace_event.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
#include "chrome/browser/apps/app_service/app_icon/dip_px_util.h"
#include "chrome/browser/apps/icon_standardizer.h"
#include "chrome/browser/extensions/chrome_app_icon.h"
#include "chrome/browser/extensions/chrome_app_icon_loader.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon_base/favicon_types.h"
#include "components/services/app_service/public/cpp/icon_types.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/component_extension_resource_manager.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/constants.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/grit/extensions_browser_resources.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_list/md_icon_normalizer.h"
#include "chrome/browser/ash/arc/icon_decode_request.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service.h"
#include "chrome/browser/ash/guest_os/guest_os_registry_service_factory.h"
#include "chrome/browser/icon_transcoder/svg_icon_transcoder.h"
#include "chrome/grit/chrome_unscaled_resources.h"
#endif
namespace {
std::string ReadFileAsCompressedString(const base::FilePath path) { … }
std::vector<uint8_t> ReadFileAsCompressedData(const base::FilePath path) { … }
#if BUILDFLAG(IS_CHROMEOS_ASH)
apps::IconValuePtr ReadAdaptiveIconFiles(apps::AdaptiveIconPaths icon_paths) {
TRACE_EVENT0("ui", "ReadAdaptiveIconFiles");
base::AssertLongCPUWorkAllowed();
auto iv = std::make_unique<apps::IconValue>();
iv->icon_type = apps::IconType::kCompressed;
iv->compressed = ReadFileAsCompressedData(icon_paths.icon_path);
iv->foreground_icon_png_data =
ReadFileAsCompressedData(icon_paths.foreground_icon_path);
iv->background_icon_png_data =
ReadFileAsCompressedData(icon_paths.background_icon_path);
return iv;
}
apps::IconValuePtr ResizeAndCompressIconOnBackgroundThread(
apps::IconValuePtr iv,
float icon_scale,
int icon_size_in_px,
SkBitmap bitmap) {
TRACE_EVENT0("ui", "ResizeAndCompressIconOnBackgroundThread");
base::AssertLongCPUWorkAllowed();
if (bitmap.width() != icon_size_in_px) {
bitmap = skia::ImageOperations::Resize(
bitmap, skia::ImageOperations::RESIZE_LANCZOS3, icon_size_in_px,
icon_size_in_px);
}
std::vector<uint8_t> encoded_image = apps::EncodeImageToPngBytes(
apps::SkBitmapToImageSkia(bitmap, icon_scale), icon_scale);
iv->compressed = std::move(encoded_image);
return iv;
}
void ResizeAndCompressIcon(apps::IconValuePtr iv,
float icon_scale,
int icon_size_in_px,
apps::LoadIconCallback result_callback,
const SkBitmap& bitmap) {
TRACE_EVENT0("ui", "ResizeAndCompressIcon");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (bitmap.drawsNothing()) {
std::move(result_callback).Run(std::move(iv));
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ResizeAndCompressIconOnBackgroundThread, std::move(iv),
icon_scale, icon_size_in_px, bitmap),
std::move(result_callback));
}
void DecodeAndResizeCompressedIcon(float icon_scale,
int icon_size_in_px,
apps::LoadIconCallback result_callback,
apps::IconValuePtr iv) {
TRACE_EVENT0("ui", "DecodeAndResizeCompressedIcon");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (iv->compressed.empty()) {
std::move(result_callback).Run(std::move(iv));
return;
}
std::vector<uint8_t> compressed_data = std::move(iv->compressed);
apps::CompressedDataToSkBitmap(
compressed_data,
base::BindOnce(&ResizeAndCompressIcon, std::move(iv), icon_scale,
icon_size_in_px, std::move(result_callback)));
}
void ReadFilesAndMaybeResize(apps::AdaptiveIconPaths icon_paths,
float icon_scale,
int icon_size_in_px,
apps::LoadIconCallback callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ReadAdaptiveIconFiles, std::move(icon_paths)),
base::BindOnce(&DecodeAndResizeCompressedIcon, icon_scale,
icon_size_in_px, std::move(callback)));
}
#endif
base::OnceCallback<void(const gfx::Image&)> ImageToImageSkia(
base::OnceCallback<void(gfx::ImageSkia)> callback) { … }
base::OnceCallback<void(const favicon_base::FaviconRawBitmapResult&)>
FaviconResultToImageSkia(base::OnceCallback<void(gfx::ImageSkia)> callback,
float icon_scale) { … }
std::optional<web_app::IconPurpose> GetIconPurpose(
const std::string& web_app_id,
const web_app::WebAppIconManager& icon_manager,
int size_hint_in_dip) { … }
apps::IconValuePtr ApplyEffects(apps::IconEffects icon_effects,
int size_hint_in_dip,
apps::IconValuePtr iv,
gfx::ImageSkia mask_image) { … }
}
namespace apps {
bool AdaptiveIconPaths::IsEmpty() { … }
AppIconLoader::AppIconLoader(Profile* profile,
std::optional<std::string> app_id,
IconType icon_type,
int size_hint_in_dip,
bool is_placeholder_icon,
apps::IconEffects icon_effects,
int fallback_icon_resource,
LoadIconCallback callback)
: … { … }
AppIconLoader::AppIconLoader(
Profile* profile,
std::optional<std::string> app_id,
IconType icon_type,
int size_hint_in_dip,
bool is_placeholder_icon,
apps::IconEffects icon_effects,
int fallback_icon_resource,
base::OnceCallback<void(LoadIconCallback)> fallback,
LoadIconCallback callback)
: … { … }
AppIconLoader::AppIconLoader(Profile* profile,
int size_hint_in_dip,
LoadIconCallback callback)
: … { … }
AppIconLoader::AppIconLoader(
int size_hint_in_dip,
base::OnceCallback<void(const gfx::ImageSkia& icon)> callback)
: … { … }
AppIconLoader::AppIconLoader(
base::OnceCallback<void(const std::vector<gfx::ImageSkia>& icons)> callback)
: … { … }
AppIconLoader::AppIconLoader(int size_hint_in_dip, LoadIconCallback callback)
: … { … }
AppIconLoader::~AppIconLoader() { … }
void AppIconLoader::ApplyIconEffects(IconEffects icon_effects,
const std::optional<std::string>& app_id,
IconValuePtr iv) { … }
void AppIconLoader::ApplyBadges(IconEffects icon_effects,
const std::optional<std::string>& app_id,
IconValuePtr iv) { … }
void AppIconLoader::LoadWebAppIcon(const std::string& web_app_id,
const GURL& launch_url,
web_app::WebAppIconManager& icon_manager) { … }
void AppIconLoader::LoadExtensionIcon(const extensions::Extension* extension) { … }
void AppIconLoader::LoadCompressedIconFromFile(const base::FilePath& path) { … }
void AppIconLoader::LoadIconFromCompressedData(
const std::string& compressed_icon_data) { … }
void AppIconLoader::LoadIconFromResource(int icon_resource) { … }
#if BUILDFLAG(IS_CHROMEOS_ASH)
void AppIconLoader::LoadArcIconPngData(
const std::vector<uint8_t>& icon_png_data) {
TRACE_EVENT0("ui", "AppIconLoader::LoadArcIconPngData");
arc_icon_decode_request_ = CreateArcIconDecodeRequest(
base::BindOnce(&AppIconLoader::ApplyBackgroundAndMask,
base::WrapRefCounted(this)),
icon_png_data);
}
void AppIconLoader::LoadCompositeImages(
const std::vector<uint8_t>& foreground_data,
const std::vector<uint8_t>& background_data) {
TRACE_EVENT0("ui", "AppIconLoader::LoadCompositeImages");
arc_foreground_icon_decode_request_ = CreateArcIconDecodeRequest(
base::BindOnce(&AppIconLoader::CompositeImagesAndApplyMask,
base::WrapRefCounted(this), true ),
foreground_data);
arc_background_icon_decode_request_ = CreateArcIconDecodeRequest(
base::BindOnce(&AppIconLoader::CompositeImagesAndApplyMask,
base::WrapRefCounted(this), false ),
background_data);
}
void AppIconLoader::LoadArcActivityIcons(
const std::vector<arc::mojom::ActivityIconPtr>& icons) {
TRACE_EVENT0("ui", "AppIconLoader::LoadArcActivityIcons");
arc_activity_icons_.resize(icons.size());
DCHECK_EQ(0U, count_);
for (size_t i = 0; i < icons.size(); i++) {
if (!icons[i] || !icons[i]->icon_png_data) {
++count_;
continue;
}
constexpr size_t kMaxIconSizeInPx = 200;
if (icons[i]->width > kMaxIconSizeInPx ||
icons[i]->height > kMaxIconSizeInPx || icons[i]->width == 0 ||
icons[i]->height == 0) {
++count_;
continue;
}
apps::ArcRawIconPngDataToImageSkia(
std::move(icons[i]->icon_png_data), icons[i]->width,
base::BindOnce(&AppIconLoader::OnArcActivityIconLoaded,
base::WrapRefCounted(this), &arc_activity_icons_[i]));
}
if (count_ == arc_activity_icons_.size() && !image_skia_callback_.is_null()) {
std::move(arc_activity_icons_callback_).Run(arc_activity_icons_);
}
}
#endif
#if BUILDFLAG(IS_CHROMEOS)
void AppIconLoader::GetWebAppCompressedIconData(
const std::string& web_app_id,
ui::ResourceScaleFactor scale_factor,
web_app::WebAppIconManager& icon_manager) {
TRACE_EVENT0("ui", "AppIconLoader::GetWebAppCompressedIconData");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::optional<web_app::IconPurpose> icon_purpose_to_read =
GetIconPurpose(web_app_id, icon_manager, size_hint_in_dip_);
if (!icon_purpose_to_read.has_value() || icon_type_ == IconType::kUnknown) {
MaybeLoadFallbackOrCompleteEmpty();
return;
}
icon_scale_ = ui::GetScaleForResourceScaleFactor(scale_factor);
icon_size_in_px_ =
apps_util::ConvertDipToPxForScale(size_hint_in_dip_, icon_scale_);
auto size_and_purpose = icon_manager.FindIconMatchBigger(
web_app_id, {*icon_purpose_to_read}, icon_size_in_px_);
DCHECK(size_and_purpose.has_value());
std::vector<int> icon_pixel_sizes;
icon_pixel_sizes.emplace_back(size_and_purpose->size_px);
icon_manager.ReadIcons(
web_app_id, *icon_purpose_to_read, icon_pixel_sizes,
base::BindOnce(&AppIconLoader::OnReadWebAppForCompressedIconData,
base::WrapRefCounted(this),
*icon_purpose_to_read == web_app::IconPurpose::MASKABLE));
}
void AppIconLoader::GetChromeAppCompressedIconData(
const extensions::Extension* extension,
ui::ResourceScaleFactor scale_factor) {
TRACE_EVENT0("ui", "AppIconLoader::GetChromeAppCompressedIconData");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(profile_);
if (!extension || icon_type_ == IconType::kUnknown) {
MaybeLoadFallbackOrCompleteEmpty();
return;
}
icon_scale_ = ui::GetScaleForResourceScaleFactor(scale_factor);
extensions::ImageLoader::Get(profile_)->LoadImageAtEveryScaleFactorAsync(
extension, gfx::Size(size_hint_in_dip_, size_hint_in_dip_),
ImageToImageSkia(
base::BindOnce(&AppIconLoader::OnReadChromeAppForCompressedIconData,
base::WrapRefCounted(this))));
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
void AppIconLoader::GetArcAppCompressedIconData(
const std::string& app_id,
ArcAppListPrefs* arc_prefs,
ui::ResourceScaleFactor scale_factor) {
TRACE_EVENT0("ui", "AppIconLoader::GetArcAppCompressedIconData");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(arc_prefs);
icon_scale_ = ui::GetScaleForResourceScaleFactor(scale_factor);
icon_size_in_px_ =
apps_util::ConvertDipToPxForScale(size_hint_in_dip_, icon_scale_);
AdaptiveIconPaths app_paths;
const ArcAppIconDescriptor descriptor(size_hint_in_dip_, scale_factor);
if (arc_prefs->IsDefault(app_id)) {
app_paths.icon_path =
arc_prefs->MaybeGetIconPathForDefaultApp(app_id, descriptor);
app_paths.foreground_icon_path =
arc_prefs->MaybeGetForegroundIconPathForDefaultApp(app_id, descriptor);
app_paths.background_icon_path =
arc_prefs->MaybeGetBackgroundIconPathForDefaultApp(app_id, descriptor);
} else {
app_paths.icon_path = arc_prefs->GetIconPath(app_id, descriptor);
app_paths.foreground_icon_path =
arc_prefs->GetForegroundIconPath(app_id, descriptor);
app_paths.background_icon_path =
arc_prefs->GetBackgroundIconPath(app_id, descriptor);
}
arc_prefs->RequestRawIconData(
app_id, ArcAppIconDescriptor(size_hint_in_dip_, scale_factor),
base::BindOnce(&AppIconLoader::OnGetArcAppCompressedIconData,
base::WrapRefCounted(this), std::move(app_paths)));
}
void AppIconLoader::GetGuestOSAppCompressedIconData(
const std::string& app_id,
ui::ResourceScaleFactor scale_factor) {
TRACE_EVENT0("ui", "AppIconLoader::GetGuestOSAppCompressedIconData");
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
CHECK(profile_);
auto* registry =
guest_os::GuestOsRegistryServiceFactory::GetForProfile(profile_);
if (!registry) {
std::move(callback_).Run(std::make_unique<apps::IconValue>());
return;
}
icon_scale_ = ui::GetScaleForResourceScaleFactor(scale_factor);
icon_size_in_px_ =
apps_util::ConvertDipToPxForScale(size_hint_in_dip_, icon_scale_);
base::FilePath png_path = registry->GetIconPath(
app_id, apps_util::GetPrimaryDisplayUIScaleFactor());
base::FilePath svg_path = registry->GetIconPath(app_id, ui::kScaleFactorNone);
registry->RequestIcon(
app_id, scale_factor,
base::BindOnce(&AppIconLoader::OnGetGuestOSAppCompressedIconData,
base::WrapRefCounted(this), png_path, svg_path));
}
void AppIconLoader::OnGetArcAppCompressedIconData(
AdaptiveIconPaths app_icon_paths,
arc::mojom::RawIconPngDataPtr icon) {
TRACE_EVENT0("ui", "AppIconLoader::OnGetArcAppCompressedIconData");
auto iv = std::make_unique<IconValue>();
if (!icon || !icon->icon_png_data.has_value()) {
if (app_icon_paths.IsEmpty()) {
std::move(callback_).Run(std::move(iv));
return;
}
ReadFilesAndMaybeResize(std::move(app_icon_paths), icon_scale_,
icon_size_in_px_, std::move(callback_));
return;
}
iv->icon_type = IconType::kCompressed;
iv->compressed = std::move(icon->icon_png_data.value());
if (icon->is_adaptive_icon) {
iv->foreground_icon_png_data =
std::move(icon->foreground_icon_png_data.value());
iv->background_icon_png_data =
std::move(icon->background_icon_png_data.value());
}
std::move(callback_).Run(std::move(iv));
}
void AppIconLoader::OnGetGuestOSAppCompressedIconData(base::FilePath png_path,
base::FilePath svg_path,
std::string icon_data) {
TRACE_EVENT0("ui", "AppIconLoader::OnGetGuestOSAppCompressedIconData");
if (!icon_data.empty()) {
std::vector<uint8_t> data(icon_data.begin(), icon_data.end());
CompressedDataToSkBitmap(
data,
base::BindOnce(&AppIconLoader::OnGetCompressedIconDataWithSkBitmap,
base::WrapRefCounted(this), false));
return;
}
if (!png_path.empty()) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(&ReadFileAsCompressedString, png_path),
base::BindOnce(&AppIconLoader::OnGetGuestOSAppCompressedIconData,
base::WrapRefCounted(this),
base::FilePath(), svg_path));
return;
}
if (!svg_path.empty()) {
TranscodeIconFromSvg(std::move(svg_path), std::move(png_path));
return;
}
std::move(callback_).Run(std::make_unique<apps::IconValue>());
}
void AppIconLoader::TranscodeIconFromSvg(base::FilePath svg_path,
base::FilePath png_path) {
TRACE_EVENT0("ui", "AppIconLoader::TranscodeIconFromSvg");
if (!profile_) {
return;
}
gfx::Size kPreferredSize = gfx::Size(128, 128);
if (!svg_icon_transcoder_) {
svg_icon_transcoder_ = std::make_unique<SvgIconTranscoder>(profile_);
}
svg_icon_transcoder_->Transcode(
std::move(svg_path), std::move(png_path), kPreferredSize,
base::BindOnce(&AppIconLoader::OnGetGuestOSAppCompressedIconData,
base::WrapRefCounted(this), base::FilePath(),
base::FilePath()));
}
std::unique_ptr<arc::IconDecodeRequest>
AppIconLoader::CreateArcIconDecodeRequest(
base::OnceCallback<void(const gfx::ImageSkia& icon)> callback,
const std::vector<uint8_t>& icon_png_data) {
TRACE_EVENT0("ui", "AppIconLoader::CreateArcIconDecodeRequest");
std::unique_ptr<arc::IconDecodeRequest> arc_icon_decode_request =
std::make_unique<arc::IconDecodeRequest>(std::move(callback),
size_hint_in_dip_);
arc_icon_decode_request->StartWithOptions(icon_png_data);
return arc_icon_decode_request;
}
void AppIconLoader::ApplyBackgroundAndMask(const gfx::ImageSkia& image) {
TRACE_EVENT0("ui", "AppIconLoader::ApplyBackgroundAndMask");
std::move(image_skia_callback_)
.Run(gfx::ImageSkiaOperations::CreateResizedImage(
apps::ApplyBackgroundAndMask(image),
skia::ImageOperations::RESIZE_LANCZOS3,
gfx::Size(size_hint_in_dip_, size_hint_in_dip_)));
}
void AppIconLoader::CompositeImagesAndApplyMask(bool is_foreground,
const gfx::ImageSkia& image) {
TRACE_EVENT0("ui", "AppIconLoader::CompositeImagesAndApplyMask");
if (is_foreground) {
foreground_is_set_ = true;
foreground_image_ = image;
} else {
background_is_set_ = true;
background_image_ = image;
}
if (!foreground_is_set_ || !background_is_set_ ||
image_skia_callback_.is_null()) {
return;
}
if (foreground_image_.isNull() || background_image_.isNull()) {
std::move(image_skia_callback_).Run(gfx::ImageSkia());
return;
}
std::move(image_skia_callback_)
.Run(gfx::ImageSkiaOperations::CreateResizedImage(
apps::CompositeImagesAndApplyMask(foreground_image_,
background_image_),
skia::ImageOperations::RESIZE_BEST,
gfx::Size(size_hint_in_dip_, size_hint_in_dip_)));
}
void AppIconLoader::OnArcActivityIconLoaded(gfx::ImageSkia* arc_activity_icon,
const gfx::ImageSkia& icon) {
TRACE_EVENT0("ui", "AppIconLoader::OnArcActivityIconLoaded");
DCHECK(arc_activity_icon);
++count_;
*arc_activity_icon = icon;
arc_activity_icon->MakeThreadSafe();
if (count_ == arc_activity_icons_.size() &&
!arc_activity_icons_callback_.is_null()) {
std::move(arc_activity_icons_callback_).Run(arc_activity_icons_);
}
}
#endif
void AppIconLoader::MaybeApplyEffectsAndComplete(const gfx::ImageSkia image) { … }
void AppIconLoader::CompleteWithCompressed(bool is_maskable_icon,
std::vector<uint8_t> data) { … }
void AppIconLoader::CompleteWithUncompressed(IconValuePtr iv) { … }
void AppIconLoader::CompleteWithIconValue(IconValuePtr iv) { … }
void AppIconLoader::OnReadWebAppIcon(std::map<int, SkBitmap> icon_bitmaps) { … }
void AppIconLoader::OnReadWebAppForCompressedIconData(
bool is_maskable_icon,
std::map<int, SkBitmap> icon_bitmaps) { … }
void AppIconLoader::OnGetCompressedIconDataWithSkBitmap(
bool is_maskable_icon,
const SkBitmap& bitmap) { … }
void AppIconLoader::OnReadChromeAppForCompressedIconData(gfx::ImageSkia image) { … }
void AppIconLoader::MaybeLoadFallbackOrCompleteEmpty() { … }
void AppIconLoader::OnProfileWillBeDestroyed(Profile* profile) { … }
}