#include "ash/clipboard/clipboard_history_resource_manager.h"
#include <string>
#include "ash/clipboard/clipboard_history.h"
#include "ash/clipboard/clipboard_history_controller_impl.h"
#include "ash/clipboard/clipboard_history_item.h"
#include "ash/clipboard/clipboard_history_util.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/clipboard_image_model_factory.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/gfx/image/image_skia.h"
namespace ash {
namespace {
using ::testing::_;
using ::testing::Bool;
using ::testing::StrictMock;
using ::testing::WithArg;
using ::testing::WithParamInterface;
class MockClipboardImageModelFactory : public ClipboardImageModelFactory {
public:
MOCK_METHOD(void,
Render,
(const base::UnguessableToken&,
const std::string&,
const gfx::Size&,
ImageModelCallback),
(override));
MOCK_METHOD(void, CancelRequest, (const base::UnguessableToken&), (override));
MOCK_METHOD(void, Activate, (), (override));
MOCK_METHOD(void, Deactivate, (), (override));
MOCK_METHOD(void, RenderCurrentPendingRequests, (), (override));
void OnShutdown() override {}
};
void FlushMessageLoop() {
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
SkBitmap GetRandomBitmap() {
SkColor color = rand() % 0xFFFFFF + 1;
SkBitmap bitmap;
bitmap.allocN32Pixels(24, 24);
bitmap.eraseARGB(255, SkColorGetR(color), SkColorGetG(color),
SkColorGetB(color));
return bitmap;
}
ui::ImageModel GetRandomImageModel() {
return ui::ImageModel::FromImageSkia(
gfx::ImageSkia::CreateFrom1xBitmap(GetRandomBitmap()));
}
}
class ClipboardHistoryResourceManagerTest : public AshTestBase {
public:
ClipboardHistoryResourceManagerTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
ClipboardHistoryResourceManagerTest(
const ClipboardHistoryResourceManagerTest&) = delete;
ClipboardHistoryResourceManagerTest& operator=(
const ClipboardHistoryResourceManagerTest&) = delete;
~ClipboardHistoryResourceManagerTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
clipboard_history_ =
Shell::Get()->clipboard_history_controller()->history();
resource_manager_ =
Shell::Get()->clipboard_history_controller()->resource_manager();
mock_image_factory_ =
std::make_unique<StrictMock<MockClipboardImageModelFactory>>();
}
const ClipboardHistory* clipboard_history() const {
return clipboard_history_;
}
const ClipboardHistoryResourceManager* resource_manager() {
return resource_manager_;
}
MockClipboardImageModelFactory* mock_image_factory() {
return mock_image_factory_.get();
}
private:
raw_ptr<const ClipboardHistory, DanglingUntriaged> clipboard_history_;
raw_ptr<const ClipboardHistoryResourceManager, DanglingUntriaged>
resource_manager_;
std::unique_ptr<MockClipboardImageModelFactory> mock_image_factory_;
};
TEST_F(ClipboardHistoryResourceManagerTest, BasicImgCachedImageModel) {
ui::ImageModel expected_image_model = GetRandomImageModel();
ON_CALL(*mock_image_factory(), Render)
.WillByDefault(WithArg<3>(
[&](ClipboardImageModelFactory::ImageModelCallback callback) {
std::move(callback).Run(expected_image_model);
}));
EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
EXPECT_CALL(*mock_image_factory(), Render).Times(1);
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"<img test>", "source_url");
}
FlushMessageLoop();
ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
const auto& item = clipboard_history()->GetItems().front();
ASSERT_TRUE(item.display_image().has_value());
EXPECT_EQ(item.display_image().value(), expected_image_model);
}
TEST_F(ClipboardHistoryResourceManagerTest, BasicTableCachedImageModel) {
ui::ImageModel expected_image_model = GetRandomImageModel();
ON_CALL(*mock_image_factory(), Render)
.WillByDefault(WithArg<3>(
[&](ClipboardImageModelFactory::ImageModelCallback callback) {
std::move(callback).Run(expected_image_model);
}));
EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
EXPECT_CALL(*mock_image_factory(), Render).Times(1);
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"<table test>", "source_url");
}
FlushMessageLoop();
ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
const auto& item = clipboard_history()->GetItems().front();
ASSERT_TRUE(item.display_image().has_value());
EXPECT_EQ(item.display_image().value(), expected_image_model);
}
TEST_F(ClipboardHistoryResourceManagerTest, BasicIneligibleCachedImageModel) {
ui::ImageModel expected_image_model = GetRandomImageModel();
ON_CALL(*mock_image_factory(), Render)
.WillByDefault(WithArg<3>(
[&](ClipboardImageModelFactory::ImageModelCallback callback) {
std::move(callback).Run(expected_image_model);
}));
EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
EXPECT_CALL(*mock_image_factory(), Render).Times(0);
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"HTML with no img or table tag", "source_url");
}
FlushMessageLoop();
ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
EXPECT_FALSE(
clipboard_history()->GetItems().front().display_image().has_value());
}
TEST_F(ClipboardHistoryResourceManagerTest, DuplicateHTML) {
ui::ImageModel expected_image_model = GetRandomImageModel();
ON_CALL(*mock_image_factory(), Render)
.WillByDefault(WithArg<3>(
[&](ClipboardImageModelFactory::ImageModelCallback callback) {
std::move(callback).Run(expected_image_model);
}));
EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
EXPECT_CALL(*mock_image_factory(), Render).Times(1);
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"<img test>", "source_url_1");
}
FlushMessageLoop();
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"<img test>", "source_url_2");
}
FlushMessageLoop();
auto items = clipboard_history()->GetItems();
EXPECT_EQ(items.size(), 2u);
for (const auto& item : items) {
ASSERT_TRUE(item.display_image().has_value());
EXPECT_EQ(item.display_image().value(), expected_image_model);
}
}
TEST_F(ClipboardHistoryResourceManagerTest, DifferentHTML) {
ui::ImageModel first_expected_image_model = GetRandomImageModel();
ui::ImageModel second_expected_image_model = GetRandomImageModel();
std::deque<ui::ImageModel> expected_image_models{first_expected_image_model,
second_expected_image_model};
ON_CALL(*mock_image_factory(), Render)
.WillByDefault(WithArg<3>(
[&](ClipboardImageModelFactory::ImageModelCallback callback) {
std::move(callback).Run(expected_image_models.front());
expected_image_models.pop_front();
}));
EXPECT_CALL(*mock_image_factory(), Render).Times(2);
EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"<img test>", "source_url");
}
FlushMessageLoop();
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"<img different>", "source_url");
}
FlushMessageLoop();
std::list<ClipboardHistoryItem> items = clipboard_history()->GetItems();
ASSERT_EQ(items.size(), 2u);
ASSERT_TRUE(items.front().display_image().has_value());
EXPECT_EQ(items.front().display_image().value(), second_expected_image_model);
items.pop_front();
ASSERT_TRUE(items.front().display_image().has_value());
EXPECT_EQ(items.front().display_image().value(), first_expected_image_model);
}
TEST_F(ClipboardHistoryResourceManagerTest, IneligibleDisplayTypes) {
EXPECT_CALL(*mock_image_factory(), Render).Times(0);
EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"<img test>", "source_url");
scw.WriteImage(GetRandomBitmap());
}
FlushMessageLoop();
ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
EXPECT_TRUE(
clipboard_history()->GetItems().front().display_image().has_value());
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteText(u"test");
scw.WriteRTF("rtf");
scw.WriteBookmark(u"bookmark_title", "test_url");
}
FlushMessageLoop();
ASSERT_EQ(clipboard_history()->GetItems().size(), 2u);
EXPECT_FALSE(
clipboard_history()->GetItems().front().display_image().has_value());
}
TEST_F(ClipboardHistoryResourceManagerTest, PlaceholderDuringRender) {
constexpr const auto kRenderDelay = base::Seconds(1);
ui::ImageModel expected_image_model = GetRandomImageModel();
ON_CALL(*mock_image_factory(), Render)
.WillByDefault(WithArg<3>(
[&](ClipboardImageModelFactory::ImageModelCallback callback) {
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(std::move(callback), expected_image_model),
kRenderDelay);
}));
EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
EXPECT_CALL(*mock_image_factory(), Render).Times(1);
base::test::TestFuture<bool> operation_confirmed_future_;
Shell::Get()
->clipboard_history_controller()
->set_confirmed_operation_callback_for_test(
operation_confirmed_future_.GetRepeatingCallback());
{
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteHTML(u"<img test>", "source_url");
}
EXPECT_TRUE(operation_confirmed_future_.Take());
ASSERT_EQ(clipboard_history()->GetItems().size(), 1u);
const auto& item = clipboard_history()->GetItems().front();
ASSERT_TRUE(item.display_image().has_value());
EXPECT_NE(item.display_image().value(), expected_image_model);
EXPECT_EQ(item.display_image().value(),
clipboard_history_util::GetHtmlPreviewPlaceholder());
task_environment()->FastForwardBy(kRenderDelay);
FlushMessageLoop();
ASSERT_TRUE(item.display_image().has_value());
EXPECT_EQ(item.display_image().value(), expected_image_model);
}
}