clipboard_history_resource_manager_unittest.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. // Copyright 2020 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/clipboard/clipboard_history_resource_manager.h"
  5. #include <string>
  6. #include <unordered_map>
  7. #include "ash/clipboard/clipboard_history.h"
  8. #include "ash/clipboard/clipboard_history_controller_impl.h"
  9. #include "ash/clipboard/clipboard_history_item.h"
  10. #include "ash/clipboard/test_support/clipboard_history_item_builder.h"
  11. #include "ash/public/cpp/clipboard_image_model_factory.h"
  12. #include "ash/shell.h"
  13. #include "ash/test/ash_test_base.h"
  14. #include "base/callback.h"
  15. #include "base/strings/utf_string_conversions.h"
  16. #include "base/test/icu_test_util.h"
  17. #include "testing/gmock/include/gmock/gmock.h"
  18. #include "testing/gtest/include/gtest/gtest.h"
  19. #include "third_party/skia/include/core/SkBitmap.h"
  20. #include "ui/base/clipboard/clipboard_data.h"
  21. #include "ui/base/clipboard/scoped_clipboard_writer.h"
  22. #include "ui/gfx/image/image_skia.h"
  23. #include "ui/gfx/image/image_unittest_util.h"
  24. namespace ash {
  25. namespace {
  26. void FlushMessageLoop() {
  27. base::RunLoop run_loop;
  28. base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE,
  29. run_loop.QuitClosure());
  30. run_loop.Run();
  31. }
  32. SkBitmap GetRandomBitmap() {
  33. SkColor color = rand() % 0xFFFFFF + 1;
  34. SkBitmap bitmap;
  35. bitmap.allocN32Pixels(24, 24);
  36. bitmap.eraseARGB(255, SkColorGetR(color), SkColorGetG(color),
  37. SkColorGetB(color));
  38. return bitmap;
  39. }
  40. ui::ImageModel GetRandomImageModel() {
  41. return ui::ImageModel::FromImageSkia(
  42. gfx::ImageSkia::CreateFrom1xBitmap(GetRandomBitmap()));
  43. }
  44. } // namespace
  45. // Tests -----------------------------------------------------------------------
  46. class MockClipboardImageModelFactory : public ClipboardImageModelFactory {
  47. public:
  48. MockClipboardImageModelFactory() = default;
  49. MockClipboardImageModelFactory(const MockClipboardImageModelFactory&) =
  50. delete;
  51. MockClipboardImageModelFactory& operator=(
  52. const MockClipboardImageModelFactory&) = delete;
  53. ~MockClipboardImageModelFactory() override = default;
  54. MOCK_METHOD(void,
  55. Render,
  56. (const base::UnguessableToken&,
  57. const std::string&,
  58. const gfx::Size&,
  59. ImageModelCallback),
  60. (override));
  61. MOCK_METHOD(void, CancelRequest, (const base::UnguessableToken&), (override));
  62. MOCK_METHOD(void, Activate, (), (override));
  63. MOCK_METHOD(void, Deactivate, (), (override));
  64. MOCK_METHOD(void, RenderCurrentPendingRequests, (), (override));
  65. void OnShutdown() override {}
  66. };
  67. class ClipboardHistoryResourceManagerTest : public AshTestBase {
  68. public:
  69. ClipboardHistoryResourceManagerTest() = default;
  70. ClipboardHistoryResourceManagerTest(
  71. const ClipboardHistoryResourceManagerTest&) = delete;
  72. ClipboardHistoryResourceManagerTest& operator=(
  73. const ClipboardHistoryResourceManagerTest&) = delete;
  74. ~ClipboardHistoryResourceManagerTest() override = default;
  75. void SetUp() override {
  76. AshTestBase::SetUp();
  77. clipboard_history_ =
  78. Shell::Get()->clipboard_history_controller()->history();
  79. resource_manager_ =
  80. Shell::Get()->clipboard_history_controller()->resource_manager();
  81. mock_image_factory_ =
  82. std::make_unique<testing::StrictMock<MockClipboardImageModelFactory>>();
  83. }
  84. const ClipboardHistory* clipboard_history() const {
  85. return clipboard_history_;
  86. }
  87. const ClipboardHistoryResourceManager* resource_manager() {
  88. return resource_manager_;
  89. }
  90. MockClipboardImageModelFactory* mock_image_factory() {
  91. return mock_image_factory_.get();
  92. }
  93. private:
  94. const ClipboardHistory* clipboard_history_;
  95. const ClipboardHistoryResourceManager* resource_manager_;
  96. std::unique_ptr<MockClipboardImageModelFactory> mock_image_factory_;
  97. };
  98. TEST_F(ClipboardHistoryResourceManagerTest, GetLabel) {
  99. base::test::ScopedRestoreICUDefaultLocale locale("en_US");
  100. // Populate a builder with all the data formats that we expect to handle.
  101. ClipboardHistoryItemBuilder builder;
  102. builder.SetText("Text")
  103. .SetMarkup("HTML with no image or table tags")
  104. .SetRtf("Rtf")
  105. .SetFilenames({ui::FileInfo(base::FilePath("/path/to/File.txt"),
  106. base::FilePath("File.txt")),
  107. ui::FileInfo(base::FilePath("/path/to/Other%20File.txt"),
  108. base::FilePath("Other%20File.txt"))})
  109. .SetBookmarkTitle("Bookmark Title")
  110. .SetPng(gfx::test::CreatePNGBytes(10))
  111. .SetFileSystemData({u"/path/to/File.txt", u"/path/to/Other%20File.txt"})
  112. .SetWebSmartPaste(true);
  113. // PNG data always take precedence.
  114. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()), u"Image");
  115. builder.ClearPng();
  116. // In the absence of PNG data, HTML data takes precedence, but we use
  117. // plain-text format for the label.
  118. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()), u"Text");
  119. builder.ClearText();
  120. // If plan-text does not exist, we show a placeholder label.
  121. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()), u"HTML Content");
  122. builder.SetText("Text");
  123. builder.ClearMarkup();
  124. // In the absence of markup data, text data takes precedence.
  125. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()), u"Text");
  126. builder.ClearText();
  127. // In the absence of HTML data, RTF data takes precedence.
  128. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()), u"RTF Content");
  129. builder.ClearRtf();
  130. // In the absence of RTF data, Filenames data takes precedence.
  131. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()),
  132. u"File.txt, Other File.txt");
  133. builder.ClearFilenames();
  134. // In the absence of RTF data, bookmark data takes precedence.
  135. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()), u"Bookmark Title");
  136. builder.ClearBookmarkTitle();
  137. // In the absence of bookmark data, web smart paste data takes precedence.
  138. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()),
  139. u"Web Smart Paste Content");
  140. builder.ClearWebSmartPaste();
  141. // In the absence of web smart paste data, file system data takes precedence.
  142. // NOTE: File system data is the only kind of custom data currently supported.
  143. EXPECT_EQ(resource_manager()->GetLabel(builder.Build()),
  144. u"File.txt, Other File.txt");
  145. }
  146. // Tests that Render is called once when an eligible <img> is added
  147. // to ClipboardHistory.
  148. TEST_F(ClipboardHistoryResourceManagerTest, BasicImgCachedImageModel) {
  149. ui::ImageModel expected_image_model = GetRandomImageModel();
  150. ON_CALL(*mock_image_factory(), Render)
  151. .WillByDefault(testing::WithArg<3>(
  152. [&](ClipboardImageModelFactory::ImageModelCallback callback) {
  153. std::move(callback).Run(expected_image_model);
  154. }));
  155. EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
  156. EXPECT_CALL(*mock_image_factory(), Render).Times(1);
  157. // Write a basic ClipboardData which is eligible to render HTML.
  158. {
  159. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  160. scw.WriteHTML(u"<img test>", "source_url");
  161. }
  162. FlushMessageLoop();
  163. EXPECT_EQ(expected_image_model, resource_manager()->GetImageModel(
  164. clipboard_history()->GetItems().front()));
  165. }
  166. // Tests that Render is called once when an eligible <table> is added
  167. // to ClipboardHistory.
  168. TEST_F(ClipboardHistoryResourceManagerTest, BasicTableCachedImageModel) {
  169. ui::ImageModel expected_image_model = GetRandomImageModel();
  170. ON_CALL(*mock_image_factory(), Render)
  171. .WillByDefault(testing::WithArg<3>(
  172. [&](ClipboardImageModelFactory::ImageModelCallback callback) {
  173. std::move(callback).Run(expected_image_model);
  174. }));
  175. EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
  176. EXPECT_CALL(*mock_image_factory(), Render).Times(1);
  177. // Write a basic ClipboardData which is eligible to render HTML.
  178. {
  179. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  180. scw.WriteHTML(u"<table test>", "source_url");
  181. }
  182. FlushMessageLoop();
  183. EXPECT_EQ(expected_image_model, resource_manager()->GetImageModel(
  184. clipboard_history()->GetItems().front()));
  185. }
  186. // Tests that Render is not called when ineligble html is added to
  187. // ClipboarHistory
  188. TEST_F(ClipboardHistoryResourceManagerTest, BasicIneligibleCachedImageModel) {
  189. ui::ImageModel expected_image_model = GetRandomImageModel();
  190. ON_CALL(*mock_image_factory(), Render)
  191. .WillByDefault(testing::WithArg<3>(
  192. [&](ClipboardImageModelFactory::ImageModelCallback callback) {
  193. std::move(callback).Run(expected_image_model);
  194. }));
  195. EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
  196. EXPECT_CALL(*mock_image_factory(), Render).Times(0);
  197. // Write a basic ClipboardData which is eligible to render HTML.
  198. {
  199. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  200. scw.WriteHTML(u"html with no img or table tag", "source_url");
  201. }
  202. FlushMessageLoop();
  203. }
  204. // Tests that copying duplicate HTML to the buffer results in only one render
  205. // request, and that that request is canceled once when the item is forgotten.
  206. TEST_F(ClipboardHistoryResourceManagerTest, DuplicateHTML) {
  207. // Write two duplicate ClipboardDatas. Two things should be in clipboard
  208. // history, but they should share a CachedImageModel.
  209. ui::ImageModel expected_image_model = GetRandomImageModel();
  210. ON_CALL(*mock_image_factory(), Render)
  211. .WillByDefault(testing::WithArg<3>(
  212. [&](ClipboardImageModelFactory::ImageModelCallback callback) {
  213. std::move(callback).Run(expected_image_model);
  214. }));
  215. EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
  216. EXPECT_CALL(*mock_image_factory(), Render).Times(1);
  217. // Use identical markup but differing source url so that both items are added
  218. // to the clipboard history.
  219. {
  220. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  221. scw.WriteHTML(u"<img test>", "source_url_1");
  222. }
  223. FlushMessageLoop();
  224. {
  225. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  226. scw.WriteHTML(u"<img test>", "source_url_2");
  227. }
  228. FlushMessageLoop();
  229. auto items = clipboard_history()->GetItems();
  230. EXPECT_EQ(2u, items.size());
  231. for (const auto& item : items)
  232. EXPECT_EQ(expected_image_model, resource_manager()->GetImageModel(item));
  233. }
  234. // Tests that two different eligible ClipboardData copied results in two calls
  235. // to Render and Cancel.
  236. TEST_F(ClipboardHistoryResourceManagerTest, DifferentHTML) {
  237. // Write two ClipboardData with different HTML.
  238. ui::ImageModel first_expected_image_model = GetRandomImageModel();
  239. ui::ImageModel second_expected_image_model = GetRandomImageModel();
  240. std::deque<ui::ImageModel> expected_image_models{first_expected_image_model,
  241. second_expected_image_model};
  242. ON_CALL(*mock_image_factory(), Render)
  243. .WillByDefault(testing::WithArg<3>(
  244. [&](ClipboardImageModelFactory::ImageModelCallback callback) {
  245. std::move(callback).Run(expected_image_models.front());
  246. expected_image_models.pop_front();
  247. }));
  248. EXPECT_CALL(*mock_image_factory(), Render).Times(2);
  249. EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
  250. {
  251. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  252. scw.WriteHTML(u"<img test>", "source_url");
  253. }
  254. FlushMessageLoop();
  255. {
  256. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  257. scw.WriteHTML(u"<img different>", "source_url");
  258. }
  259. FlushMessageLoop();
  260. std::list<ClipboardHistoryItem> items = clipboard_history()->GetItems();
  261. EXPECT_EQ(2u, items.size());
  262. EXPECT_EQ(second_expected_image_model,
  263. resource_manager()->GetImageModel(items.front()));
  264. items.pop_front();
  265. EXPECT_EQ(first_expected_image_model,
  266. resource_manager()->GetImageModel(items.front()));
  267. }
  268. // Tests that items that are ineligible for CachedImageModels (items with image
  269. // representations, or no markup) do not request Render.
  270. TEST_F(ClipboardHistoryResourceManagerTest, IneligibleItem) {
  271. // Write a ClipboardData with an image, no CachedImageModel should be created.
  272. EXPECT_CALL(*mock_image_factory(), Render).Times(0);
  273. EXPECT_CALL(*mock_image_factory(), CancelRequest).Times(0);
  274. {
  275. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  276. scw.WriteHTML(u"test", "source_url");
  277. scw.WriteImage(GetRandomBitmap());
  278. }
  279. FlushMessageLoop();
  280. EXPECT_EQ(1u, clipboard_history()->GetItems().size());
  281. // Write a ClipboardData with no markup and no image. No CachedImageModel
  282. // should be created.
  283. {
  284. ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
  285. scw.WriteText(u"test");
  286. scw.WriteRTF("rtf");
  287. scw.WriteBookmark(u"bookmark_title", "test_url");
  288. }
  289. FlushMessageLoop();
  290. EXPECT_EQ(2u, clipboard_history()->GetItems().size());
  291. }
  292. } // namespace ash