text: // 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/ui/thumbnails/thumbnail_image.h"
  
  #include <memory>
  #include <utility>
  
  #include "base/bind.h"
  #include "base/macros.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/abseil-cpp/absl/types/optional.h"
  #include "ui/gfx/image/image_skia.h"
  
  namespace {
  
  constexpr int kTestBitmapWidth = 200;
  constexpr int kTestBitmapHeight = 123;
  
  template <typename... T>
  base::RepeatingCallback<void(T...)> IgnoreArgs(
      base::RepeatingCallback<void()> cb) {
    auto helper = [](T...) {};
    return base::BindRepeating(helper).Then(std::move(cb));
  }
  
  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;
  
   protected:
    static SkBitmap CreateBitmap(int width, int height) {
      SkBitmap bitmap;
      bitmap.allocN32Pixels(width, height);
      bitmap.eraseARGB(255, 0, 255, 0);
      return bitmap;
    }
  
    std::vector<uint8_t> Compress(SkBitmap bitmap) const {
      return ThumbnailImage::CompressBitmap(bitmap, absl::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_;
    DISALLOW_COPY_AND_ASSIGN(ThumbnailImageTest);
  };
  
  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(
        IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
  
    CallbackWaiter waiter2;
    subscription2->SetUncompressedImageCallback(
        IgnoreArgs<gfx::ImageSkia>(waiter2.callback()));
  
    SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::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(
        IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
  
    CallbackWaiter waiter2;
    subscription2->SetUncompressedImageCallback(
        IgnoreArgs<gfx::ImageSkia>(waiter2.callback()));
  
    SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::nullopt);
  
    waiter1.Wait();
    waiter2.Wait();
    EXPECT_TRUE(waiter1.called());
    EXPECT_TRUE(waiter2.called());
  
    waiter1.Reset();
    waiter2.Reset();
  
    image->AssignSkBitmap(bitmap, absl::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(
        IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter1.callback()));
  
    CallbackWaiter waiter2;
    subscription2->SetCompressedImageCallback(
        IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter2.callback()));
  
    SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::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(
        IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter1.callback()));
  
    CallbackWaiter waiter2;
    subscription2->SetCompressedImageCallback(
        IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter2.callback()));
  
    SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::nullopt);
  
    waiter1.Wait();
    waiter2.Wait();
    EXPECT_TRUE(waiter1.called());
    EXPECT_TRUE(waiter2.called());
  
    waiter1.Reset();
    waiter2.Reset();
  
    image->AssignSkBitmap(bitmap, absl::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(
        IgnoreArgs<gfx::ImageSkia>(waiter1.callback()));
  
    SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::nullopt);
    waiter1.Wait();
    EXPECT_TRUE(waiter1.called());
    waiter1.Reset();
  
    std::unique_ptr<Subscription> subscription2 = image->Subscribe();
  
    CallbackWaiter waiter2;
    subscription2->SetUncompressedImageCallback(
        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(
        IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(waiter.callback()));
  
    SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::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(
        IgnoreArgs<gfx::ImageSkia>(uncompressed_image_waiter.callback()));
  
    CallbackWaiter compressed_image_waiter;
    subscription->SetCompressedImageCallback(
        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 = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::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(
        IgnoreArgs<gfx::ImageSkia>(uncompressed_image_waiter.callback()));
  
    CallbackWaiter compressed_image_waiter;
    subscription->SetCompressedImageCallback(
        IgnoreArgs<ThumbnailImage::CompressedThumbnailData>(
            compressed_image_waiter.callback()));
  
    SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::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(
        IgnoreArgs<gfx::ImageSkia>(uncompressed_image_waiter.callback()));
  
    SkBitmap bitmap = CreateBitmap(kTestBitmapWidth, kTestBitmapHeight);
    image->AssignSkBitmap(bitmap, absl::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());
  }
  