blob: bb4a56ec4eb5ab6d161fb5abf59890b33ae5dd76 [file] [log] [blame]
// Copyright 2019 The Chromium 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 "chrome/browser/web_applications/extensions/bookmark_app_icon_manager.h"
#include <map>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/notreached.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/extensions/menu_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/extensions/bookmark_app_util.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/extension_icon_set.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/common/manifest_handlers/web_app_shortcut_icons_handler.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
namespace extensions {
namespace {
void OnExtensionIconLoaded(BookmarkAppIconManager::ReadIconCallback callback,
const gfx::Image& image) {
std::move(callback).Run(image.IsEmpty() ? SkBitmap() : *image.ToSkBitmap());
}
const Extension* GetBookmarkApp(Profile* profile,
const web_app::AppId& app_id) {
const Extension* extension =
ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(app_id);
return (extension && extension->from_bookmark()) ? extension : nullptr;
}
void ReadExtensionIcon(Profile* profile,
const web_app::AppId& app_id,
SquareSizePx icon_size_in_px,
ExtensionIconSet::MatchType match_type,
BookmarkAppIconManager::ReadIconCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const Extension* extension = GetBookmarkApp(profile, app_id);
DCHECK(extension);
ImageLoader* loader = ImageLoader::Get(profile);
loader->LoadImageAsync(
extension,
IconsInfo::GetIconResource(extension, icon_size_in_px, match_type),
gfx::Size(icon_size_in_px, icon_size_in_px),
base::BindOnce(&OnExtensionIconLoaded, std::move(callback)));
}
void OnExtensionIconsLoaded(BookmarkAppIconManager::ReadIconsCallback callback,
const gfx::Image& image) {
std::map<SquareSizePx, SkBitmap> icons_map;
gfx::ImageSkia image_skia = image.AsImageSkia();
for (const gfx::ImageSkiaRep& image_skia_rep : image_skia.image_reps())
icons_map[image_skia_rep.pixel_width()] = image_skia_rep.GetBitmap();
std::move(callback).Run(std::move(icons_map));
}
void ReadExtensionIcons(Profile* profile,
const web_app::AppId& app_id,
const std::vector<SquareSizePx>& icon_sizes_in_px,
BookmarkAppIconManager::ReadIconsCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const Extension* app = GetBookmarkApp(profile, app_id);
DCHECK(app);
std::vector<ImageLoader::ImageRepresentation> info_list;
for (SquareSizePx size_in_px : icon_sizes_in_px) {
ExtensionResource resource = IconsInfo::GetIconResource(
app, size_in_px, ExtensionIconSet::MATCH_EXACTLY);
ImageLoader::ImageRepresentation image_rep{
resource, ImageLoader::ImageRepresentation::NEVER_RESIZE,
gfx::Size{size_in_px, size_in_px}, /*scale_factor=*/0.0f};
info_list.push_back(image_rep);
}
ImageLoader* loader = ImageLoader::Get(profile);
loader->LoadImagesAsync(
app, info_list,
base::BindOnce(&OnExtensionIconsLoaded, std::move(callback)));
}
SkBitmap ReadShortcutsMenuIconBlocking(const base::FilePath& path) {
// Read icon data from disk.
std::string icon_data;
if (path.empty() || !base::ReadFileToString(path, &icon_data)) {
return SkBitmap();
}
SkBitmap bitmap;
if (!gfx::PNGCodec::Decode(
reinterpret_cast<const unsigned char*>(icon_data.c_str()),
icon_data.size(), &bitmap)) {
return SkBitmap();
}
return bitmap;
}
// Performs blocking I/O. May be called on another thread.
ShortcutsMenuIconBitmaps ReadShortcutsMenuIconsBlocking(
const std::vector<std::vector<ImageLoader::ImageRepresentation>>&
shortcuts_menu_images_reps) {
ShortcutsMenuIconBitmaps results;
for (const auto& image_reps : shortcuts_menu_images_reps) {
IconBitmaps result;
for (const auto& image_rep : image_reps) {
SkBitmap bitmap =
ReadShortcutsMenuIconBlocking(image_rep.resource.GetFilePath());
if (!bitmap.empty())
result.any[image_rep.desired_size.width()] = std::move(bitmap);
}
// We always push_back (even when result is empty) to keep a given
// std::map's index in sync with that of its corresponding shortcuts menu
// item.
results.push_back(std::move(result));
}
return results;
}
std::vector<std::vector<ImageLoader::ImageRepresentation>>
CreateShortcutsMenuIconsImageRepresentations(
Profile* profile,
const web_app::AppId& app_id,
const std::vector<IconSizes>& shortcuts_menu_icons_sizes) {
const Extension* web_app = GetBookmarkApp(profile, app_id);
DCHECK(web_app);
std::vector<std::vector<ImageLoader::ImageRepresentation>> results;
for (size_t i = 0; i < shortcuts_menu_icons_sizes.size(); ++i) {
std::vector<ImageLoader::ImageRepresentation> result;
for (const auto& icon_size : shortcuts_menu_icons_sizes[i].any) {
ExtensionResource resource = WebAppShortcutIconsInfo::GetIconResource(
web_app, i, icon_size, ExtensionIconSet::MATCH_EXACTLY);
ImageLoader::ImageRepresentation image_rep{
resource, ImageLoader::ImageRepresentation::NEVER_RESIZE,
gfx::Size{icon_size, icon_size}, /*scale_factor=*/0.0f};
result.emplace_back(std::move(image_rep));
}
results.emplace_back(std::move(result));
}
return results;
}
void WrapCallbackAsPurposeAny(
BookmarkAppIconManager::ReadIconBitmapsCallback callback,
std::map<SquareSizePx, SkBitmap> icon_bitmaps) {
IconBitmaps result;
result.any = std::move(icon_bitmaps);
std::move(callback).Run(result);
}
} // anonymous namespace
BookmarkAppIconManager::BookmarkAppIconManager(Profile* profile)
: profile_(profile) {}
BookmarkAppIconManager::~BookmarkAppIconManager() = default;
void BookmarkAppIconManager::Start() {}
void BookmarkAppIconManager::Shutdown() {}
bool BookmarkAppIconManager::HasIcons(
const web_app::AppId& app_id,
IconPurpose purpose,
const SortedSizesPx& icon_sizes_in_px) const {
const Extension* app = GetBookmarkApp(profile_, app_id);
if (!app)
return false;
if (icon_sizes_in_px.empty())
return true;
// Legacy bookmark apps handle IconPurpose::ANY icons only.
if (purpose != IconPurpose::ANY)
return false;
const ExtensionIconSet& icons = IconsInfo::GetIcons(app);
for (SquareSizePx size_in_px : icon_sizes_in_px) {
const std::string& path =
icons.Get(size_in_px, ExtensionIconSet::MATCH_EXACTLY);
if (path.empty())
return false;
}
return true;
}
base::Optional<web_app::AppIconManager::IconSizeAndPurpose>
BookmarkAppIconManager::FindIconMatchBigger(
const web_app::AppId& app_id,
const std::vector<IconPurpose>& purposes,
SquareSizePx min_size) const {
const Extension* app = GetBookmarkApp(profile_, app_id);
if (!app)
return base::nullopt;
// Legacy bookmark apps handle IconPurpose::ANY icons only.
if (!base::Contains(purposes, IconPurpose::ANY))
return base::nullopt;
const ExtensionIconSet& icons = IconsInfo::GetIcons(app);
const std::string& path = icons.Get(min_size, ExtensionIconSet::MATCH_BIGGER);
// Returns 0 if path is empty or not found.
int found_icon_size = icons.GetIconSizeFromPath(path);
if (found_icon_size == 0)
return base::nullopt;
return IconSizeAndPurpose{found_icon_size, IconPurpose::ANY};
}
bool BookmarkAppIconManager::HasSmallestIcon(
const web_app::AppId& app_id,
const std::vector<IconPurpose>& purposes,
SquareSizePx min_size) const {
return FindIconMatchBigger(app_id, purposes, min_size).has_value();
}
void BookmarkAppIconManager::ReadIcons(const web_app::AppId& app_id,
IconPurpose purpose,
const SortedSizesPx& icon_sizes_in_px,
ReadIconsCallback callback) const {
DCHECK(HasIcons(app_id, purpose, icon_sizes_in_px));
// Legacy bookmark apps handle IconPurpose::ANY icons only.
if (purpose != IconPurpose::ANY) {
std::move(callback).Run(std::map<SquareSizePx, SkBitmap>());
return;
}
const std::vector<SquareSizePx> icon_sizes_vector(icon_sizes_in_px.begin(),
icon_sizes_in_px.end());
ReadExtensionIcons(profile_, app_id, icon_sizes_vector, std::move(callback));
}
void BookmarkAppIconManager::ReadAllIcons(
const web_app::AppId& app_id,
ReadIconBitmapsCallback callback) const {
const Extension* app = GetBookmarkApp(profile_, app_id);
DCHECK(app);
ReadIconsCallback wrapped_callback =
base::BindOnce(WrapCallbackAsPurposeAny, std::move(callback));
ReadExtensionIcons(profile_, app_id, GetBookmarkAppDownloadedIconSizes(app),
std::move(wrapped_callback));
}
void BookmarkAppIconManager::ReadAllShortcutsMenuIcons(
const web_app::AppId& app_id,
ReadShortcutsMenuIconsCallback callback) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const Extension* web_app = GetBookmarkApp(profile_, app_id);
DCHECK(web_app);
if (!web_app) {
std::move(callback).Run(ShortcutsMenuIconBitmaps{});
return;
}
std::vector<std::vector<ImageLoader::ImageRepresentation>> img_reps =
CreateShortcutsMenuIconsImageRepresentations(
profile_, app_id,
GetBookmarkAppDownloadedShortcutsMenuIconsSizes(web_app));
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(ReadShortcutsMenuIconsBlocking, std::move(img_reps)),
std::move(callback));
}
void BookmarkAppIconManager::ReadSmallestIcon(
const web_app::AppId& app_id,
const std::vector<IconPurpose>& purposes,
SquareSizePx icon_size_in_px,
ReadIconWithPurposeCallback callback) const {
DCHECK(HasSmallestIcon(app_id, purposes, icon_size_in_px));
ReadIconCallback wrapped = base::BindOnce(
WrapReadIconWithPurposeCallback, std::move(callback), IconPurpose::ANY);
ReadExtensionIcon(profile_, app_id, icon_size_in_px,
ExtensionIconSet::MATCH_BIGGER, std::move(wrapped));
}
void BookmarkAppIconManager::ReadSmallestCompressedIcon(
const web_app::AppId& app_id,
const std::vector<IconPurpose>& purposes,
SquareSizePx icon_size_in_px,
ReadCompressedIconWithPurposeCallback callback) const {
NOTIMPLEMENTED();
DCHECK(HasSmallestIcon(app_id, purposes, icon_size_in_px));
std::move(callback).Run(IconPurpose::ANY, std::vector<uint8_t>());
}
SkBitmap BookmarkAppIconManager::GetFavicon(
const web_app::AppId& app_id) const {
auto* menu_manager = extensions::MenuManager::Get(profile_);
return menu_manager->GetIconForExtension(app_id).AsBitmap();
}
} // namespace extensions