blob: 2d051713bc514e2fc3125ce656b24df4060b8c78 [file] [log] [blame]
// Copyright 2019 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/ui/thumbnails/thumbnail_image.h"
#include <memory>
#include <optional>
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_unittest_util.h"
namespace {
constexpr int kTestBitmapWidth = 200;
constexpr int kTestBitmapHeight = 123;
class CallbackWaiter {
public:
CallbackWaiter() {
callback_ = base::BindRepeating(&CallbackWaiter::HandleCallback,
weak_ptr_factory_.GetWeakPtr());
}
base::RepeatingClosure callback() { return callback_; }
bool called() const { return called_; }
void Reset() { called_ = false; }
void Wait() {
if (called_) {
return;
}
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
private:
void HandleCallback() {
if (quit_closure_) {
std::move(quit_closure_).Run();
}
called_ = true;
}
base::RepeatingClosure callback_;
base::OnceClosure quit_closure_;
bool called_ = false;
base::WeakPtrFactory<CallbackWaiter> weak_ptr_factory_{this};
};
class StubDelegate : public ThumbnailImage::Delegate {
public:
StubDelegate() = default;
~StubDelegate() override = default;
// ThumbnailImage::Delegate:
void ThumbnailImageBeingObservedChanged(bool is_being_observed) override {}
};
} // anonymous namespace
class ThumbnailImageTest : public testing::Test,
public ThumbnailImage::Delegate {
public:
ThumbnailImageTest() = default;
ThumbnailImageTest(const ThumbnailImageTest&) = delete;
ThumbnailImageTest& operator=(const ThumbnailImageTest&) = delete;
protected:
std::vector<uint8_t> Compress(SkBitmap bitmap) const {
return ThumbnailImage::CompressBitmap(bitmap, std::nullopt);
}
bool is_being_observed() const { return is_being_observed_; }
private:
void ThumbnailImageBeingObservedChanged(bool is_being_observed) override {
is_being_observed_ = is_being_observed;
}
bool is_being_observed_ = false;
base::test::TaskEnvironment task_environment_;
};
using Subscription = ThumbnailImage::Subscription;
TEST_F(ThumbnailImageTest, AddRemoveSubscriber) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
EXPECT_FALSE(is_being_observed());
std::unique_ptr<Subscription> subscription = image->Subscribe();
EXPECT_TRUE(is_being_observed());
subscription.reset();
EXPECT_FALSE(is_being_observed());
}
TEST_F(ThumbnailImageTest, AddRemoveMultipleObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
EXPECT_FALSE(is_being_observed());
std::unique_ptr<Subscription> subscription1 = image->Subscribe();
EXPECT_TRUE(is_being_observed());
std::unique_ptr<Subscription> subscription2 = image->Subscribe();
EXPECT_TRUE(is_being_observed());
subscription1.reset();
EXPECT_TRUE(is_being_observed());
subscription2.reset();
EXPECT_FALSE(is_being_observed());
}
TEST_F(ThumbnailImageTest, AssignSkBitmapNotifiesObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription1 = image->Subscribe();
std::unique_ptr<Subscription> subscription2 = image->Subscribe();
CallbackWaiter waiter1;
subscription1->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
CallbackWaiter waiter2;
subscription2->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(waiter2.callback()));
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
}
TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesObserversAgain) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription1 = image->Subscribe();
std::unique_ptr<Subscription> subscription2 = image->Subscribe();
CallbackWaiter waiter1;
subscription1->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
CallbackWaiter waiter2;
subscription2->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(waiter2.callback()));
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap, std::nullopt);
waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
waiter1.Reset();
waiter2.Reset();
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
}
TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesCompressedObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription1 = image->Subscribe();
std::unique_ptr<Subscription> subscription2 = image->Subscribe();
CallbackWaiter waiter1;
subscription1->SetCompressedImageCallback(
base::IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(
waiter1.callback()));
CallbackWaiter waiter2;
subscription2->SetCompressedImageCallback(
base::IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(
waiter2.callback()));
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
}
TEST_F(ThumbnailImageTest, AssignSkBitmap_NotifiesCompressedObserversAgain) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription1 = image->Subscribe();
std::unique_ptr<Subscription> subscription2 = image->Subscribe();
CallbackWaiter waiter1;
subscription1->SetCompressedImageCallback(
base::IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(
waiter1.callback()));
CallbackWaiter waiter2;
subscription2->SetCompressedImageCallback(
base::IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(
waiter2.callback()));
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(bitmap, std::nullopt);
waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
waiter1.Reset();
waiter2.Reset();
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
}
TEST_F(ThumbnailImageTest, RequestThumbnailImage) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription1 = image->Subscribe();
CallbackWaiter waiter1;
subscription1->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
waiter1.Wait();
EXPECT_TRUE(waiter1.called());
waiter1.Reset();
std::unique_ptr<Subscription> subscription2 = image->Subscribe();
CallbackWaiter waiter2;
subscription2->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(waiter2.callback()));
image->RequestThumbnailImage();
waiter1.Wait();
waiter2.Wait();
EXPECT_TRUE(waiter1.called());
EXPECT_TRUE(waiter2.called());
}
TEST_F(ThumbnailImageTest, RequestCompressedThumbnailData) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription = image->Subscribe();
CallbackWaiter waiter;
subscription->SetCompressedImageCallback(
base::IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(
waiter.callback()));
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
waiter.Wait();
EXPECT_TRUE(waiter.called());
waiter.Reset();
image->RequestCompressedThumbnailData();
waiter.Wait();
EXPECT_TRUE(waiter.called());
}
TEST_F(ThumbnailImageTest, ClearThumbnailAfterAssignBitmap) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription = image->Subscribe();
CallbackWaiter uncompressed_image_waiter;
subscription->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(uncompressed_image_waiter.callback()));
CallbackWaiter compressed_image_waiter;
subscription->SetCompressedImageCallback(
base::IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(
compressed_image_waiter.callback()));
CallbackWaiter async_operation_finished_waiter;
image->set_async_operation_finished_callback_for_testing(
async_operation_finished_waiter.callback());
// No observers should be notified if the thumbnail is cleared just
// after assigning a bitmap.
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
image->ClearData();
async_operation_finished_waiter.Wait();
EXPECT_TRUE(async_operation_finished_waiter.called());
EXPECT_FALSE(uncompressed_image_waiter.called());
EXPECT_FALSE(compressed_image_waiter.called());
}
TEST_F(ThumbnailImageTest, ClearExistingThumbnailNotifiesObservers) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription = image->Subscribe();
CallbackWaiter uncompressed_image_waiter;
subscription->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(uncompressed_image_waiter.callback()));
CallbackWaiter compressed_image_waiter;
subscription->SetCompressedImageCallback(
base::IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(
compressed_image_waiter.callback()));
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
compressed_image_waiter.Wait();
uncompressed_image_waiter.Wait();
EXPECT_TRUE(compressed_image_waiter.called());
EXPECT_TRUE(uncompressed_image_waiter.called());
compressed_image_waiter.Reset();
uncompressed_image_waiter.Reset();
image->ClearData();
compressed_image_waiter.Wait();
uncompressed_image_waiter.Wait();
EXPECT_TRUE(compressed_image_waiter.called());
EXPECT_TRUE(uncompressed_image_waiter.called());
}
// Makes sure a null dereference does not happen. Regression test for
// crbug.com/1159701.
TEST_F(ThumbnailImageTest, UnsubscribeAfterDelegateDestroyed) {
auto delegate = std::make_unique<StubDelegate>();
auto image = base::MakeRefCounted<ThumbnailImage>(delegate.get());
std::unique_ptr<Subscription> subscription = image->Subscribe();
// Normally |image| will notify its delegate when the last
// subscription is destroyed. When there is no delegate it shouldn't
// do anything.
delegate.reset();
subscription.reset();
}
// Ensures subscribers with a size hint get notified correctly on
// thumbnail clear. Regression test for crbug.com/1168483 where
// CropPreviewImage was called on blank thumbnails resulting in a
// DCHECK.
TEST_F(ThumbnailImageTest, DoesNotCropBlankThumbnails) {
auto image = base::MakeRefCounted<ThumbnailImage>(this);
std::unique_ptr<Subscription> subscription = image->Subscribe();
subscription->SetSizeHint(
gfx::Size(kTestBitmapWidth / 2, kTestBitmapHeight / 2));
CallbackWaiter uncompressed_image_waiter;
subscription->SetUncompressedImageCallback(
base::IgnoreArgs<gfx::ImageSkia>(uncompressed_image_waiter.callback()));
SkBitmap bitmap =
gfx::test::CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
image->AssignSkBitmap(std::move(bitmap), std::nullopt);
uncompressed_image_waiter.Wait();
EXPECT_TRUE(uncompressed_image_waiter.called());
uncompressed_image_waiter.Reset();
image->ClearData();
uncompressed_image_waiter.Wait();
EXPECT_TRUE(uncompressed_image_waiter.called());
}