// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/icon_loader.h"
#include <windows.h>
#include <shellapi.h>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread.h"
#include "chrome/browser/win/icon_reader_service.h"
#include "chrome/services/util_win/public/mojom/util_read_icon.mojom.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ui/display/win/dpi.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/icon_util.h"
#include "ui/gfx/image/image_skia.h"
namespace {
// Helper class to manage lifetime of icon reader service.
class IconLoaderHelper {
public:
static void ExecuteLoadIcon(
base::FilePath filename,
base::File file,
chrome::mojom::IconSize size,
float scale,
gfx::Image default_icon,
scoped_refptr<base::SingleThreadTaskRunner> target_task_runner,
IconLoader::IconLoadedCallback icon_loaded_callback);
IconLoaderHelper(base::FilePath filename,
chrome::mojom::IconSize size,
float scale,
gfx::Image default_icon);
IconLoaderHelper(const IconLoaderHelper&) = delete;
IconLoaderHelper& operator=(const IconLoaderHelper&) = delete;
private:
void StartReadIconRequest(base::File file);
void OnConnectionError();
void OnReadIconExecuted(const gfx::ImageSkia& icon);
using IconLoaderHelperCallback =
base::OnceCallback<void(gfx::Image image,
const IconLoader::IconGroup& icon_group)>;
void set_finally(IconLoaderHelperCallback finally) {
finally_ = std::move(finally);
}
mojo::Remote<chrome::mojom::UtilReadIcon> remote_read_icon_;
base::FilePath filename_;
chrome::mojom::IconSize size_;
const float scale_;
// This callback owns the object until work is done.
IconLoaderHelperCallback finally_;
gfx::Image default_icon_;
SEQUENCE_CHECKER(sequence_checker_);
};
void IconLoaderHelper::ExecuteLoadIcon(
base::FilePath filename,
base::File file,
chrome::mojom::IconSize size,
float scale,
gfx::Image default_icon,
scoped_refptr<base::SingleThreadTaskRunner> target_task_runner,
IconLoader::IconLoadedCallback icon_loaded_callback) {
// Self-deleting helper manages service lifetime.
auto helper = std::make_unique<IconLoaderHelper>(filename, size, scale,
std::move(default_icon));
auto* helper_raw = helper.get();
// This callback owns the helper and extinguishes itself once work is done.
auto finally_callback = base::BindOnce(
[](std::unique_ptr<IconLoaderHelper> helper,
IconLoader::IconLoadedCallback icon_loaded_callback,
scoped_refptr<base::SingleThreadTaskRunner> target_task_runner,
gfx::Image image, const IconLoader::IconGroup& icon_group) {
target_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(icon_loaded_callback),
std::move(image), icon_group));
},
std::move(helper), std::move(icon_loaded_callback), target_task_runner);
helper_raw->set_finally(std::move(finally_callback));
helper_raw->StartReadIconRequest(std::move(file));
}
IconLoaderHelper::IconLoaderHelper(base::FilePath filename,
chrome::mojom::IconSize size,
float scale,
gfx::Image default_icon)
: filename_(filename),
size_(size),
scale_(scale),
default_icon_(std::move(default_icon)) {
remote_read_icon_ = LaunchIconReaderInstance();
remote_read_icon_.set_disconnect_handler(base::BindOnce(
&IconLoaderHelper::OnConnectionError, base::Unretained(this)));
}
void IconLoaderHelper::StartReadIconRequest(base::File file) {
remote_read_icon_->ReadIcon(
std::move(file), size_, scale_,
base::BindOnce(&IconLoaderHelper::OnReadIconExecuted,
base::Unretained(this)));
}
void IconLoaderHelper::OnConnectionError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (finally_.is_null())
return;
std::move(finally_).Run(std::move(default_icon_), filename_.value());
}
void IconLoaderHelper::OnReadIconExecuted(const gfx::ImageSkia& icon) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::wstring icon_group = filename_.value();
if (icon.isNull()) {
std::move(finally_).Run(std::move(default_icon_), icon_group);
} else {
gfx::Image image(icon);
std::move(finally_).Run(std::move(image), icon_group);
}
}
// Must be called in a COM context. |group| should be a file extension.
gfx::Image GetIconForFileExtension(const std::wstring& group,
IconLoader::IconSize icon_size) {
int size = 0;
switch (icon_size) {
case IconLoader::SMALL:
size = SHGFI_SMALLICON;
break;
case IconLoader::NORMAL:
size = 0;
break;
case IconLoader::LARGE:
size = SHGFI_LARGEICON;
break;
default:
NOTREACHED_IN_MIGRATION();
}
gfx::Image image;
// Not only is GetFileInfo a blocking call, it's also known to hang
// (crbug.com/1249943), add a ScopedBlockingCall to let the scheduler know
// when this hangs and to explicitly label this call in tracing.
base::ScopedBlockingCall blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
SHFILEINFO file_info = {0};
if (SHGetFileInfo(group.c_str(), FILE_ATTRIBUTE_NORMAL, &file_info,
sizeof(file_info),
SHGFI_ICON | size | SHGFI_USEFILEATTRIBUTES)) {
const SkBitmap bitmap = IconUtil::CreateSkBitmapFromHICON(file_info.hIcon);
if (!bitmap.isNull()) {
gfx::ImageSkia image_skia(
gfx::ImageSkiaRep(bitmap, display::win::GetDPIScale()));
image_skia.MakeThreadSafe();
image = gfx::Image(image_skia);
}
DestroyIcon(file_info.hIcon);
}
return image;
}
} // namespace
// static
IconLoader::IconGroup IconLoader::GroupForFilepath(
const base::FilePath& file_path) {
if (file_path.MatchesExtension(L".exe") ||
file_path.MatchesExtension(L".dll") ||
file_path.MatchesExtension(L".ico")) {
return file_path.value();
}
return file_path.Extension();
}
// static
scoped_refptr<base::TaskRunner> IconLoader::GetReadIconTaskRunner() {
// Technically speaking, only a thread with COM is needed, not one that has
// a COM STA. However, this is what is available for now.
return base::ThreadPool::CreateCOMSTATaskRunner(traits());
}
void IconLoader::ReadGroup() {
group_ = GroupForFilepath(file_path_);
if (group_ == file_path_.value()) {
// Calls a Windows API that parses the file so must be sandboxed.
GetReadIconTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&IconLoader::ReadIconInSandbox, base::Unretained(this)));
} else {
// Looks up generic icons for groups based only on the file's extension.
GetReadIconTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&IconLoader::ReadIcon, base::Unretained(this)));
}
}
void IconLoader::ReadIcon() {
auto image = GetIconForFileExtension(group_, icon_size_);
target_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback_), std::move(image), group_));
delete this;
}
void IconLoader::ReadIconInSandbox() {
// Get default first as loader is deleted before ExecuteLoadIcon
// completes.
auto path = base::FilePath(group_);
auto default_icon = GetIconForFileExtension(path.Extension(), icon_size_);
chrome::mojom::IconSize size = chrome::mojom::IconSize::kNormal;
switch (icon_size_) {
case IconLoader::SMALL:
size = chrome::mojom::IconSize::kSmall;
break;
case IconLoader::NORMAL:
size = chrome::mojom::IconSize::kNormal;
break;
case IconLoader::LARGE:
size = chrome::mojom::IconSize::kLarge;
break;
default:
NOTREACHED_IN_MIGRATION();
}
base::File file;
file.Initialize(path, base::File::FLAG_READ |
base::File::FLAG_WIN_SHARE_DELETE |
base::File::FLAG_OPEN);
if (file.IsValid()) {
target_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&IconLoaderHelper::ExecuteLoadIcon, std::move(path),
std::move(file), size, scale_, std::move(default_icon),
target_task_runner_, std::move(callback_)));
} else {
target_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), std::move(default_icon),
path.value()));
}
delete this;
}