910e62b5创建于 1月15日历史提交
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/clipboard/clipboard_non_backed.h"

#include <memory>
#include <string>
#include <vector>

#include "base/pickle.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/clipboard/clipboard_data.h"
#include "ui/base/clipboard/custom_data_helper.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/skia_util.h"

#if BUILDFLAG(IS_OZONE)
#include "base/command_line.h"
#include "ui/gfx/switches.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/ozone_switches.h"
#endif  // BUILDFLAG(IS_OZONE)

namespace ui {
namespace {

std::vector<std::string> UTF8Types(std::vector<std::u16string> types) {
  std::vector<std::string> result;
  for (const std::u16string& type : types)
    result.push_back(base::UTF16ToUTF8(type));
  return result;
}

}  // namespace

// Base class for tests of `ClipboardNonBacked`.
class ClipboardNonBackedTestBase : public testing::Test {
 public:
  explicit ClipboardNonBackedTestBase(
      base::test::TaskEnvironment::TimeSource time_source)
      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI,
                          time_source) {}
  void SetUp() override {
#if BUILDFLAG(IS_OZONE)
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    command_line->AppendSwitchASCII(::switches::kOzonePlatform,
                                    switches::kHeadless);
    ui::OzonePlatform::PreEarlyInitialization();
#endif  // BUILDFLAG(IS_OZONE)

    // Clipboard needs to be instantiated after Ozone is initialized so that
    // ui::Clipboard::Create() can call ui::OzonePlatform::GetInstance().
    std::unique_ptr<Clipboard> clipboard(new ClipboardNonBacked());
    Clipboard::SetClipboardForCurrentThread(std::move(clipboard));
  }

  void TearDown() override { Clipboard::DestroyClipboardForCurrentThread(); }

  ClipboardNonBacked* clipboard() {
    return ClipboardNonBacked::GetForCurrentThread();
  }

 protected:
  base::test::TaskEnvironment task_environment_;
};

// Base class for tests of `ClipboardNonBacked` which use system time.
class ClipboardNonBackedTest : public ClipboardNonBackedTestBase {
 public:
  ClipboardNonBackedTest()
      : ClipboardNonBackedTestBase(
            base::test::TaskEnvironment::TimeSource::SYSTEM_TIME) {}
};

// Verifies that GetClipboardData() returns the same instance of ClipboardData
// as was written via WriteClipboardData().
TEST_F(ClipboardNonBackedTest, WriteAndGetClipboardData) {
  auto clipboard_data = std::make_unique<ClipboardData>();

  auto* expected_clipboard_data_ptr = clipboard_data.get();
  clipboard()->WriteClipboardData(std::move(clipboard_data));
  auto* actual_clipboard_data_ptr = clipboard()->GetClipboardData(nullptr);

  EXPECT_EQ(expected_clipboard_data_ptr, actual_clipboard_data_ptr);
}

// Verifies that WriteClipboardData() writes a ClipboardData instance to the
// clipboard and returns the previous instance.
TEST_F(ClipboardNonBackedTest, WriteClipboardData) {
  auto first_data = std::make_unique<ClipboardData>();
  auto second_data = std::make_unique<ClipboardData>();

  auto* first_data_ptr = first_data.get();
  auto* second_data_ptr = second_data.get();

  auto previous_data = clipboard()->WriteClipboardData(std::move(first_data));
  EXPECT_EQ(previous_data.get(), nullptr);

  previous_data = clipboard()->WriteClipboardData(std::move(second_data));

  EXPECT_EQ(first_data_ptr, previous_data.get());
  EXPECT_EQ(second_data_ptr, clipboard()->GetClipboardData(nullptr));
}

// Verifies that directly writing to ClipboardInternal does not result in
// histograms being logged. This is used by ClipboardHistoryController to
// manipulate the clipboard in order to facilitate pasting from clipboard
// history.
TEST_F(ClipboardNonBackedTest, AdminWriteDoesNotRecordHistograms) {
  base::HistogramTester histogram_tester;
  auto data = std::make_unique<ClipboardData>();
  data->set_text("test");

  auto* data_ptr = data.get();

  // Write the data to the clipboard, no histograms should be recorded.
  clipboard()->WriteClipboardData(std::move(data));
  EXPECT_EQ(data_ptr, clipboard()->GetClipboardData(/*data_dst=*/nullptr));

  histogram_tester.ExpectTotalCount("Clipboard.Read", 0);
  histogram_tester.ExpectTotalCount("Clipboard.Write", 0);
}

// Tests that text data uses 'text/plain' mime type.
TEST_F(ClipboardNonBackedTest, PlainText) {
  auto data = std::make_unique<ClipboardData>();
  data->set_text("hello");
  clipboard()->WriteClipboardData(std::move(data));
  std::vector<std::u16string> types;
  clipboard()->ReadAvailableTypes(ClipboardBuffer::kCopyPaste,
                                  /*data_dst=*/nullptr, &types);

  // Text data uses mime type 'text/plain'.
  EXPECT_EQ(std::vector<std::string>({"text/plain"}), UTF8Types(types));
  EXPECT_TRUE(clipboard()->IsFormatAvailable(
      ClipboardFormatType::PlainTextType(), ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr));

  // Validate reading back the text.
  std::u16string text;
  clipboard()->ReadText(ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr,
                        &text);
  EXPECT_EQ(u"hello", text);
}

// Tests that site bookmark URLs are accessed as text, and
// IsFormatAvailable('text/uri-list') is only true for files.
TEST_F(ClipboardNonBackedTest, BookmarkURL) {
  auto data = std::make_unique<ClipboardData>();
  data->set_bookmark_title("Example Page");
  data->set_bookmark_url("http://example.com");
  clipboard()->WriteClipboardData(std::move(data));
  std::vector<std::u16string> types;
  clipboard()->ReadAvailableTypes(ClipboardBuffer::kCopyPaste,
                                  /*data_dst=*/nullptr, &types);

  // Bookmark data returns available type 'text/plain'.
  EXPECT_EQ(std::vector<std::string>({"text/plain"}), UTF8Types(types));
  EXPECT_TRUE(clipboard()->IsFormatAvailable(
      ClipboardFormatType::PlainTextType(), ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr));
  EXPECT_TRUE(clipboard()->IsFormatAvailable(ClipboardFormatType::UrlType(),
                                             ClipboardBuffer::kCopyPaste,
                                             /*data_dst=*/nullptr));
  EXPECT_TRUE(clipboard()->IsFormatAvailable(
      ClipboardFormatType::PlainTextType(), ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr));
  EXPECT_FALSE(clipboard()->IsFormatAvailable(
      ClipboardFormatType::FilenamesType(), ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr));

  // Validate reading back the bookmark.
  std::u16string title;
  std::string url;
  clipboard()->ReadBookmark(/*data_dst=*/nullptr, &title, &url);
  EXPECT_EQ(u"Example Page", title);
  EXPECT_EQ("http://example.com", url);
  std::u16string text;
  clipboard()->ReadText(ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr,
                        &text);
  EXPECT_EQ(u"http://example.com", text);
}

// Filenames data uses mime type 'text/uri-list'.
TEST_F(ClipboardNonBackedTest, TextURIList) {
  auto data = std::make_unique<ClipboardData>();
  data->set_filenames(
      {FileInfo(base::FilePath(FILE_PATH_LITERAL("/path")), base::FilePath())});
  clipboard()->WriteClipboardData(std::move(data));
  std::vector<std::u16string> types;
  clipboard()->ReadAvailableTypes(ClipboardBuffer::kCopyPaste,
                                  /*data_dst=*/nullptr, &types);
  EXPECT_EQ(std::vector<std::string>({"text/uri-list"}), UTF8Types(types));
  EXPECT_FALSE(clipboard()->IsFormatAvailable(ClipboardFormatType::UrlType(),
                                              ClipboardBuffer::kCopyPaste,
                                              /*data_dst=*/nullptr));
  EXPECT_TRUE(clipboard()->IsFormatAvailable(
      ClipboardFormatType::FilenamesType(), ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr));

  // Filenames data uses mime type 'text/uri-list', but clients can also set
  // 'text/uri-list' as one of the custom data types. When it is set as a custom
  // type, it will be returned from ReadAvailableTypes(), but
  // IsFormatAvailable(FilenamesType()) is false.
  data = std::make_unique<ClipboardData>();
  base::flat_map<std::u16string, std::u16string> custom_data;
  custom_data[u"text/uri-list"] = u"data";
  base::Pickle pickle;
  ui::WriteCustomDataToPickle(custom_data, &pickle);
  data->SetCustomData(ui::ClipboardFormatType::DataTransferCustomType(),
                      std::string(pickle.data_as_char(), pickle.size()));
  clipboard()->WriteClipboardData(std::move(data));
  clipboard()->ReadAvailableTypes(ClipboardBuffer::kCopyPaste,
                                  /*data_dst=*/nullptr, &types);
  EXPECT_EQ(std::vector<std::string>({"text/uri-list"}), UTF8Types(types));
  EXPECT_FALSE(clipboard()->IsFormatAvailable(
      ClipboardFormatType::FilenamesType(), ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr));
}

// Tests that bitmaps written to the clipboard are read out encoded as a PNG.
TEST_F(ClipboardNonBackedTest, ImageEncoding) {
  auto data = std::make_unique<ClipboardData>();
  SkBitmap test_bitmap = gfx::test::CreateBitmap(3, 2);
  data->SetBitmapData(test_bitmap);
  clipboard()->WriteClipboardData(std::move(data));

  std::vector<std::u16string> types;
  clipboard()->ReadAvailableTypes(ClipboardBuffer::kCopyPaste,
                                  /*data_dst=*/nullptr, &types);
  EXPECT_EQ(std::vector<std::string>({"image/png"}), UTF8Types(types));
  EXPECT_TRUE(clipboard()->IsFormatAvailable(ClipboardFormatType::PngType(),
                                             ClipboardBuffer::kCopyPaste,
                                             /*data_dst=*/nullptr));

  // Asynchronously read out the image as a PNG. It should be the encoded
  // version of the bitmap we wrote above.
  std::vector<uint8_t> png;
  base::RunLoop loop;
  clipboard()->ReadPng(
      ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr,
      base::BindLambdaForTesting([&](const std::vector<uint8_t>& png_data) {
        png = png_data;
        loop.Quit();
      }));
  loop.Run();

  SkBitmap bitmap = gfx::PNGCodec::Decode(png);
  ASSERT_FALSE(bitmap.isNull());
  EXPECT_TRUE(gfx::BitmapsAreEqual(bitmap, test_bitmap));
}

// Tests that consecutive calls to read an image from the clipboard only results
// in the image being encoded once.
TEST_F(ClipboardNonBackedTest, EncodeImageOnce) {
  auto data = std::make_unique<ClipboardData>();
  SkBitmap test_bitmap = gfx::test::CreateBitmap(3, 2);
  data->SetBitmapData(test_bitmap);
  clipboard()->WriteClipboardData(std::move(data));

  std::vector<std::u16string> types;
  clipboard()->ReadAvailableTypes(ClipboardBuffer::kCopyPaste,
                                  /*data_dst=*/nullptr, &types);
  EXPECT_EQ(std::vector<std::string>({"image/png"}), UTF8Types(types));
  EXPECT_TRUE(clipboard()->IsFormatAvailable(ClipboardFormatType::PngType(),
                                             ClipboardBuffer::kCopyPaste,
                                             /*data_dst=*/nullptr));

  std::vector<std::vector<uint8_t>> pngs;
  base::RunLoop loop;
  // Read from the clipboard many times in a row.
  clipboard()->ReadPng(
      ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr,
      base::BindLambdaForTesting([&](const std::vector<uint8_t>& png_data) {
        pngs.emplace_back(png_data);
      }));
  clipboard()->ReadPng(
      ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr,
      base::BindLambdaForTesting([&](const std::vector<uint8_t>& png_data) {
        pngs.emplace_back(png_data);
      }));
  clipboard()->ReadPng(
      ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr,
      base::BindLambdaForTesting([&](const std::vector<uint8_t>& png_data) {
        pngs.emplace_back(png_data);
        // Read operations should be ordered. This callback will be called last.
        loop.Quit();
      }));
  loop.Run();

  ASSERT_EQ(pngs.size(), 3u);
  EXPECT_EQ(pngs[0], pngs[1]);
  EXPECT_EQ(pngs[0], pngs[2]);

  // The bitmap should only have been encoded once.
  EXPECT_EQ(clipboard()->NumImagesEncodedForTesting(), 1);

  SkBitmap bitmap = gfx::PNGCodec::Decode(pngs[0]);
  ASSERT_FALSE(bitmap.isNull());
  EXPECT_TRUE(gfx::BitmapsAreEqual(bitmap, test_bitmap));
}

// Tests that consecutive calls to read an image from the clipboard only results
// in the image being encoded once, but if another image is placed on the
// clipboard, this image is appropriately encoded.
TEST_F(ClipboardNonBackedTest, EncodeMultipleImages) {
  auto data = std::make_unique<ClipboardData>();
  SkBitmap test_bitmap = gfx::test::CreateBitmap(3, 2);
  data->SetBitmapData(test_bitmap);

  auto data2 = std::make_unique<ClipboardData>();
  SkBitmap test_bitmap2 = gfx::test::CreateBitmap(4, 3);
  data2->SetBitmapData(test_bitmap2);

  clipboard()->WriteClipboardData(std::move(data));

  std::vector<std::u16string> types;
  clipboard()->ReadAvailableTypes(ClipboardBuffer::kCopyPaste,
                                  /*data_dst=*/nullptr, &types);
  EXPECT_EQ(std::vector<std::string>({"image/png"}), UTF8Types(types));
  EXPECT_TRUE(clipboard()->IsFormatAvailable(ClipboardFormatType::PngType(),
                                             ClipboardBuffer::kCopyPaste,
                                             /*data_dst=*/nullptr));

  std::vector<std::vector<uint8_t>> pngs;
  base::RunLoop loop;
  // Read from the clipboard many times in a row.
  clipboard()->ReadPng(
      ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr,
      base::BindLambdaForTesting([&](const std::vector<uint8_t>& png_data) {
        pngs.emplace_back(png_data);
      }));
  clipboard()->ReadPng(
      ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr,
      base::BindLambdaForTesting([&](const std::vector<uint8_t>& png_data) {
        pngs.emplace_back(png_data);
      }));

  // Write a different image to the clipboard.
  clipboard()->WriteClipboardData(std::move(data2));

  clipboard()->ReadPng(
      ClipboardBuffer::kCopyPaste,
      /*data_dst=*/nullptr,
      base::BindLambdaForTesting([&](const std::vector<uint8_t>& png_data) {
        pngs.emplace_back(png_data);
        // Read operations should be ordered. This callback will be called last.
        loop.Quit();
      }));
  loop.Run();

  ASSERT_EQ(pngs.size(), 3u);
  EXPECT_EQ(pngs[0], pngs[1]);
  EXPECT_NE(pngs[0], pngs[2]);

  // The first bitmap should only have been encoded once, but the second bitmap
  // should have been encoded separately.
  EXPECT_EQ(clipboard()->NumImagesEncodedForTesting(), 2);

  SkBitmap bitmap = gfx::PNGCodec::Decode(pngs[0]);
  ASSERT_FALSE(bitmap.isNull());
  EXPECT_TRUE(gfx::BitmapsAreEqual(bitmap, test_bitmap));
  bitmap = gfx::PNGCodec::Decode(pngs[2]);
  ASSERT_FALSE(bitmap.isNull());
  EXPECT_TRUE(gfx::BitmapsAreEqual(bitmap, test_bitmap2));
}

// Tests that different clipboard buffers have independent data.
TEST_F(ClipboardNonBackedTest, ClipboardBufferTypes) {
  static const struct ClipboardBufferInfo {
    ui::ClipboardBuffer buffer;
    std::u16string paste_text;
  } clipboard_buffers[] = {
      {ui::ClipboardBuffer::kCopyPaste, u"kCopyPaste"},
      {ui::ClipboardBuffer::kSelection, u"kSelection"},
      {ui::ClipboardBuffer::kDrag, u"kDrag"},
  };

  // Check basic write/read ops into each buffer type.
  for (const auto& [buffer, paste_text] : clipboard_buffers) {
    if (ui::Clipboard::IsSupportedClipboardBuffer(buffer)) {
      ScopedClipboardWriter(buffer).WriteText(paste_text);

      std::u16string copy_text;
      clipboard()->ReadText(buffer, /*data_dst=*/nullptr, &copy_text);
      EXPECT_EQ(paste_text, copy_text);
    }
  }

  // Verify that different clipboard buffer data is independent.
  for (const auto& [buffer, paste_text] : clipboard_buffers) {
    if (ui::Clipboard::IsSupportedClipboardBuffer(buffer)) {
      std::u16string copy_text;
      clipboard()->ReadText(buffer, /*data_dst=*/nullptr, &copy_text);
      EXPECT_EQ(paste_text, copy_text);
    }
  }
}

#if BUILDFLAG(IS_CHROMEOS)
// Base class for tests of `ClipboardNonBacked` which use mock time.
class ClipboardNonBackedMockTimeTest : public ClipboardNonBackedTestBase {
 public:
  ClipboardNonBackedMockTimeTest()
      : ClipboardNonBackedTestBase(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
};

// Verifies that `ClipboardNonBacked` records the time interval between commit
// and read of the same `ClipboardData`.
TEST_F(ClipboardNonBackedMockTimeTest,
       RecordsTimeIntervalBetweenCommitAndRead) {
  // Cache clipboard for the current thread.
  auto* clipboard = ClipboardNonBacked::GetForCurrentThread();
  ASSERT_TRUE(clipboard);

  // Write clipboard data to the clipboard.
  ScopedClipboardWriter(ClipboardBuffer::kCopyPaste).WriteText(u"");
  auto* clipboard_data = clipboard->GetClipboardData(/*data_dst=*/nullptr);
  ASSERT_TRUE(clipboard_data);
  ASSERT_TRUE(clipboard_data->commit_time().has_value());
  EXPECT_EQ(clipboard_data->commit_time().value(), base::Time::Now());

  // This test will verify expectations for every kind of clipboard data read.
  std::vector<base::RepeatingClosure> test_cases = {{
      base::BindLambdaForTesting([&]() {
        clipboard->ReadAsciiText(
            ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr,
            /*result=*/std::make_unique<std::string>().get());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadBookmark(
            /*data_dst=*/nullptr,
            /*title=*/std::make_unique<std::u16string>().get(),
            /*url=*/std::make_unique<std::string>().get());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadDataTransferCustomData(
            ClipboardBuffer::kCopyPaste,
            /*type=*/std::u16string(), /*data_dst=*/nullptr,
            /*result=*/std::make_unique<std::u16string>().get());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadData(ui::ClipboardFormatType(),
                            /*data_dst=*/nullptr,
                            /*result=*/std::make_unique<std::string>().get());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadFilenames(
            ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr,
            /*result=*/std::make_unique<std::vector<ui::FileInfo>>().get());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadHTML(
            ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr,
            /*markup=*/std::make_unique<std::u16string>().get(),
            /*src_url=*/std::make_unique<std::string>().get(),
            /*fragment_start=*/std::make_unique<uint32_t>().get(),
            /*fragment_end=*/std::make_unique<uint32_t>().get());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadPng(ClipboardBuffer::kCopyPaste,
                           /*data_dst=*/nullptr,
                           /*callback=*/base::DoNothing());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadRTF(ClipboardBuffer::kCopyPaste,
                           /*data_dst=*/nullptr,
                           /*result=*/std::make_unique<std::string>().get());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadSvg(ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr,
                           /*result=*/std::make_unique<std::u16string>().get());
      }),
      base::BindLambdaForTesting([&]() {
        clipboard->ReadText(
            ClipboardBuffer::kCopyPaste, /*data_dst=*/nullptr,
            /*result=*/std::make_unique<std::u16string>().get());
      }),
  }};

  // Read clipboard data and verify histogram expectations.
  constexpr base::TimeDelta kTimeDelta = base::Seconds(10);
  constexpr char kHistogram[] = "Clipboard.TimeIntervalBetweenCommitAndRead";
  for (size_t i = 1u; i <= test_cases.size(); ++i) {
    base::HistogramTester histogram_tester;
    histogram_tester.ExpectUniqueTimeSample(kHistogram, i * kTimeDelta, 0u);
    task_environment_.FastForwardBy(kTimeDelta);
    test_cases.at(i - 1u).Run();
    histogram_tester.ExpectUniqueTimeSample(kHistogram, i * kTimeDelta, 1u);
  }
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace ui