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 
5 #include "chrome/browser/paint_preview/services/paint_preview_tab_service.h"
6 
7 #include <memory>
8 #include <utility>
9 #include <vector>
10 
11 #include "base/files/file_enumerator.h"
12 #include "base/files/file_util.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/test/task_environment.h"
15 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
16 #include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
17 #include "content/public/browser/render_process_host.h"
18 #include "content/public/test/navigation_simulator.h"
19 #include "content/public/test/test_utils.h"
20 #include "mojo/public/cpp/bindings/associated_receiver.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
23 
24 namespace paint_preview {
25 
26 namespace {
27 
28 constexpr char kFeatureName[] = "tab_service_test";
29 
30 class MockPaintPreviewRecorder : public mojom::PaintPreviewRecorder {
31  public:
32   MockPaintPreviewRecorder() = default;
33   ~MockPaintPreviewRecorder() override = default;
34 
35   MockPaintPreviewRecorder(const MockPaintPreviewRecorder&) = delete;
36   MockPaintPreviewRecorder& operator=(const MockPaintPreviewRecorder&) = delete;
37 
CapturePaintPreview(mojom::PaintPreviewCaptureParamsPtr params,mojom::PaintPreviewRecorder::CapturePaintPreviewCallback callback)38   void CapturePaintPreview(
39       mojom::PaintPreviewCaptureParamsPtr params,
40       mojom::PaintPreviewRecorder::CapturePaintPreviewCallback callback)
41       override {
42     std::move(callback).Run(status_, mojom::PaintPreviewCaptureResponse::New());
43   }
44 
SetResponse(mojom::PaintPreviewStatus status)45   void SetResponse(mojom::PaintPreviewStatus status) { status_ = status; }
46 
BindRequest(mojo::ScopedInterfaceEndpointHandle handle)47   void BindRequest(mojo::ScopedInterfaceEndpointHandle handle) {
48     binding_.Bind(mojo::PendingAssociatedReceiver<mojom::PaintPreviewRecorder>(
49         std::move(handle)));
50   }
51 
52  private:
53   mojom::PaintPreviewStatus status_;
54   mojo::AssociatedReceiver<mojom::PaintPreviewRecorder> binding_{this};
55 };
56 
ListDir(const base::FilePath & path)57 std::vector<base::FilePath> ListDir(const base::FilePath& path) {
58   base::FileEnumerator enumerator(
59       path, false,
60       base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES,
61       FILE_PATH_LITERAL("*.skp"));  // Ignore the proto.pb files.
62   std::vector<base::FilePath> files;
63   for (base::FilePath name = enumerator.Next(); !name.empty();
64        name = enumerator.Next()) {
65     files.push_back(name);
66   }
67   return files;
68 }
69 
70 }  // namespace
71 
72 class PaintPreviewTabServiceTest : public ChromeRenderViewHostTestHarness {
73  public:
PaintPreviewTabServiceTest()74   PaintPreviewTabServiceTest()
75       : ChromeRenderViewHostTestHarness(
76             base::test::TaskEnvironment::TimeSource::MOCK_TIME,
77             base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {}
78   ~PaintPreviewTabServiceTest() override = default;
79 
80   PaintPreviewTabServiceTest(const PaintPreviewTabServiceTest&) = delete;
81   PaintPreviewTabServiceTest& operator=(const PaintPreviewTabServiceTest&) =
82       delete;
83 
84  protected:
SetUp()85   void SetUp() override {
86     ChromeRenderViewHostTestHarness::SetUp();
87     NavigateAndCommit(GURL("https://www.example.com/"),
88                       ui::PageTransition::PAGE_TRANSITION_FIRST);
89     task_environment()->RunUntilIdle();
90     EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
91     service_ = std::make_unique<PaintPreviewTabService>(
92         temp_dir_.GetPath(), kFeatureName, nullptr, false);
93     task_environment()->RunUntilIdle();
94     EXPECT_TRUE(service_->CacheInitialized());
95   }
96 
TearDown()97   void TearDown() override { ChromeRenderViewHostTestHarness::TearDown(); }
98 
GetService()99   PaintPreviewTabService* GetService() { return service_.get(); }
100 
OverrideInterface(MockPaintPreviewRecorder * recorder)101   void OverrideInterface(MockPaintPreviewRecorder* recorder) {
102     blink::AssociatedInterfaceProvider* remote_interfaces =
103         web_contents()->GetMainFrame()->GetRemoteAssociatedInterfaces();
104     remote_interfaces->OverrideBinderForTesting(
105         mojom::PaintPreviewRecorder::Name_,
106         base::BindRepeating(&MockPaintPreviewRecorder::BindRequest,
107                             base::Unretained(recorder)));
108   }
109 
GetPath() const110   const base::FilePath& GetPath() const { return temp_dir_.GetPath(); }
111 
BuildServiceWithCache(const std::vector<int> & tab_ids)112   std::unique_ptr<PaintPreviewTabService> BuildServiceWithCache(
113       const std::vector<int>& tab_ids) {
114     auto path =
115         GetPath().AppendASCII("paint_preview").AppendASCII(kFeatureName);
116     std::string fake_content = "foobarbaz";
117 
118     for (const auto& i : tab_ids) {
119       auto key_path = path.AppendASCII(base::NumberToString(i));
120       EXPECT_TRUE(base::CreateDirectory(key_path));
121       EXPECT_EQ(static_cast<size_t>(
122                     base::WriteFile(key_path.AppendASCII("proto.pb"),
123                                     fake_content.data(), fake_content.size())),
124                 fake_content.size());
125     }
126 
127     return std::make_unique<PaintPreviewTabService>(GetPath(), kFeatureName,
128                                                     nullptr, false);
129   }
130 
131  private:
132   std::unique_ptr<PaintPreviewTabService> service_;
133   base::ScopedTempDir temp_dir_;
134 };
135 
TEST_F(PaintPreviewTabServiceTest,CaptureTab)136 TEST_F(PaintPreviewTabServiceTest, CaptureTab) {
137   const int kTabId = 1U;
138 
139   MockPaintPreviewRecorder recorder;
140   recorder.SetResponse(mojom::PaintPreviewStatus::kOk);
141   OverrideInterface(&recorder);
142 
143   auto* service = GetService();
144   service->CaptureTab(kTabId, web_contents(),
145                       base::BindOnce([](PaintPreviewTabService::Status status) {
146                         EXPECT_EQ(status, PaintPreviewTabService::Status::kOk);
147                       }));
148   task_environment()->RunUntilIdle();
149   EXPECT_TRUE(service->HasCaptureForTab(kTabId));
150 
151   auto file_manager = service->GetFileManager();
152   auto key = file_manager->CreateKey(kTabId);
153   service->GetTaskRunner()->PostTaskAndReplyWithResult(
154       FROM_HERE,
155       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
156       base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
157   task_environment()->RunUntilIdle();
158 
159   service->TabClosed(kTabId);
160   EXPECT_FALSE(service->HasCaptureForTab(kTabId));
161   task_environment()->RunUntilIdle();
162   service->GetTaskRunner()->PostTaskAndReplyWithResult(
163       FROM_HERE,
164       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
165       base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
166   task_environment()->RunUntilIdle();
167 }
168 
TEST_F(PaintPreviewTabServiceTest,CaptureTabFailed)169 TEST_F(PaintPreviewTabServiceTest, CaptureTabFailed) {
170   const int kTabId = 1U;
171 
172   MockPaintPreviewRecorder recorder;
173   recorder.SetResponse(mojom::PaintPreviewStatus::kFailed);
174   OverrideInterface(&recorder);
175 
176   auto* service = GetService();
177   service->CaptureTab(
178       kTabId, web_contents(),
179       base::BindOnce([](PaintPreviewTabService::Status status) {
180         EXPECT_EQ(status, PaintPreviewTabService::Status::kCaptureFailed);
181       }));
182   task_environment()->RunUntilIdle();
183 
184   auto file_manager = service->GetFileManager();
185   auto key = file_manager->CreateKey(kTabId);
186   service->GetTaskRunner()->PostTaskAndReplyWithResult(
187       FROM_HERE,
188       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
189       base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
190   task_environment()->RunUntilIdle();
191   EXPECT_FALSE(service->HasCaptureForTab(kTabId));
192 
193   service->TabClosed(kTabId);
194   task_environment()->RunUntilIdle();
195   service->GetTaskRunner()->PostTaskAndReplyWithResult(
196       FROM_HERE,
197       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
198       base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
199   task_environment()->RunUntilIdle();
200   EXPECT_FALSE(service->HasCaptureForTab(kTabId));
201 }
202 
TEST_F(PaintPreviewTabServiceTest,CaptureTabTwice)203 TEST_F(PaintPreviewTabServiceTest, CaptureTabTwice) {
204   const int kTabId = 1U;
205 
206   MockPaintPreviewRecorder recorder;
207   recorder.SetResponse(mojom::PaintPreviewStatus::kOk);
208   OverrideInterface(&recorder);
209 
210   auto* service = GetService();
211   service->CaptureTab(kTabId, web_contents(),
212                       base::BindOnce([](PaintPreviewTabService::Status status) {
213                         EXPECT_EQ(status, PaintPreviewTabService::Status::kOk);
214                       }));
215   task_environment()->RunUntilIdle();
216   auto file_manager = service->GetFileManager();
217   auto key = file_manager->CreateKey(kTabId);
218   service->GetTaskRunner()->PostTaskAndReplyWithResult(
219       FROM_HERE,
220       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
221       base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
222   task_environment()->RunUntilIdle();
223   base::FilePath path_1;
224   service->GetTaskRunner()->PostTaskAndReplyWithResult(
225       FROM_HERE,
226       base::BindOnce(&FileManager::CreateOrGetDirectory, file_manager, key,
227                      false),
228       base::BindOnce(
229           [](base::FilePath* out, const base::Optional<base::FilePath>& path) {
230             EXPECT_TRUE(path.has_value());
231             *out = path.value();
232           },
233           &path_1));
234   task_environment()->RunUntilIdle();
235   auto files_1 = ListDir(path_1);
236   ASSERT_EQ(1U, files_1.size());
237 
238   service->CaptureTab(kTabId, web_contents(),
239                       base::BindOnce([](PaintPreviewTabService::Status status) {
240                         EXPECT_EQ(status, PaintPreviewTabService::Status::kOk);
241                       }));
242   task_environment()->RunUntilIdle();
243 
244   service->GetTaskRunner()->PostTaskAndReplyWithResult(
245       FROM_HERE,
246       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
247       base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
248   base::FilePath path_2;
249   service->GetTaskRunner()->PostTaskAndReplyWithResult(
250       FROM_HERE,
251       base::BindOnce(&FileManager::CreateOrGetDirectory, file_manager, key,
252                      false),
253       base::BindOnce(
254           [](base::FilePath* out, const base::Optional<base::FilePath>& path) {
255             EXPECT_TRUE(path.has_value());
256             *out = path.value();
257           },
258           &path_2));
259   task_environment()->RunUntilIdle();
260   EXPECT_EQ(path_2, path_1);
261   auto files_2 = ListDir(path_2);
262   ASSERT_EQ(1U, files_2.size());
263   // The embedding token is used in the filename of the captured SkPicture.
264   // Since the embedding token is the same the filenames should also be the
265   // same.
266   EXPECT_EQ(files_1, files_2);
267   EXPECT_TRUE(service->HasCaptureForTab(kTabId));
268 
269   service->TabClosed(kTabId);
270   service->GetTaskRunner()->PostTaskAndReplyWithResult(
271       FROM_HERE,
272       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
273       base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
274   task_environment()->RunUntilIdle();
275   EXPECT_FALSE(service->HasCaptureForTab(kTabId));
276 }
277 
TEST_F(PaintPreviewTabServiceTest,TestUnityAudit)278 TEST_F(PaintPreviewTabServiceTest, TestUnityAudit) {
279   std::vector<int> tab_ids = {1, 2, 3};
280   auto service = BuildServiceWithCache(tab_ids);
281   auto file_manager = service->GetFileManager();
282   task_environment()->RunUntilIdle();
283 
284   service->AuditArtifacts(tab_ids);
285   task_environment()->RunUntilIdle();
286 
287   for (const auto& id : tab_ids) {
288     EXPECT_TRUE(service->HasCaptureForTab(id));
289     auto key = file_manager->CreateKey(id);
290     service->GetTaskRunner()->PostTaskAndReplyWithResult(
291         FROM_HERE,
292         base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
293         base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
294   }
295   task_environment()->RunUntilIdle();
296 }
297 
TEST_F(PaintPreviewTabServiceTest,TestDisjointAudit)298 TEST_F(PaintPreviewTabServiceTest, TestDisjointAudit) {
299   std::vector<int> tab_ids = {1, 2, 3};
300   auto service = BuildServiceWithCache(tab_ids);
301   auto file_manager = service->GetFileManager();
302   task_environment()->RunUntilIdle();
303 
304   service->AuditArtifacts({4});
305   task_environment()->RunUntilIdle();
306 
307   for (const auto& id : tab_ids) {
308     EXPECT_FALSE(service->HasCaptureForTab(id));
309     auto key = file_manager->CreateKey(id);
310     service->GetTaskRunner()->PostTaskAndReplyWithResult(
311         FROM_HERE,
312         base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
313         base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
314   }
315   task_environment()->RunUntilIdle();
316 }
317 
TEST_F(PaintPreviewTabServiceTest,TestPartialAudit)318 TEST_F(PaintPreviewTabServiceTest, TestPartialAudit) {
319   auto service = BuildServiceWithCache({1, 2, 3});
320   auto file_manager = service->GetFileManager();
321   task_environment()->RunUntilIdle();
322 
323   std::vector<int> kept_tab_ids = {1, 3};
324   service->AuditArtifacts(kept_tab_ids);
325   task_environment()->RunUntilIdle();
326 
327   for (const auto& id : kept_tab_ids) {
328     EXPECT_TRUE(service->HasCaptureForTab(id));
329     auto key = file_manager->CreateKey(id);
330     service->GetTaskRunner()->PostTaskAndReplyWithResult(
331         FROM_HERE,
332         base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
333         base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
334   }
335   EXPECT_FALSE(service->HasCaptureForTab(2));
336   auto key = file_manager->CreateKey(2);
337   service->GetTaskRunner()->PostTaskAndReplyWithResult(
338       FROM_HERE,
339       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
340       base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
341   task_environment()->RunUntilIdle();
342 }
343 
TEST_F(PaintPreviewTabServiceTest,LoadCache)344 TEST_F(PaintPreviewTabServiceTest, LoadCache) {
345   auto service = BuildServiceWithCache({1, 3, 4});
346   task_environment()->RunUntilIdle();
347 
348   EXPECT_TRUE(service->CacheInitialized());
349   EXPECT_TRUE(service->HasCaptureForTab(1));
350   EXPECT_FALSE(service->HasCaptureForTab(2));
351   EXPECT_TRUE(service->HasCaptureForTab(3));
352   EXPECT_TRUE(service->HasCaptureForTab(4));
353   EXPECT_FALSE(service->HasCaptureForTab(5));
354 }
355 
TEST_F(PaintPreviewTabServiceTest,EarlyDeletion)356 TEST_F(PaintPreviewTabServiceTest, EarlyDeletion) {
357   auto service = BuildServiceWithCache({1, 3});
358   // This should queue a deferred deletion so that the cache is in the right
359   // state.
360   service->TabClosed(1);
361   EXPECT_FALSE(service->CacheInitialized());
362   EXPECT_FALSE(service->HasCaptureForTab(1));
363   task_environment()->RunUntilIdle();
364   task_environment()->AdvanceClock(base::TimeDelta::FromSeconds(10));
365   task_environment()->RunUntilIdle();
366 
367   EXPECT_TRUE(service->CacheInitialized());
368   EXPECT_FALSE(service->HasCaptureForTab(1));
369   EXPECT_TRUE(service->HasCaptureForTab(3));
370 }
371 
TEST_F(PaintPreviewTabServiceTest,EarlyAudit)372 TEST_F(PaintPreviewTabServiceTest, EarlyAudit) {
373   auto service = BuildServiceWithCache({1, 3});
374   // This should queue a deferred deletion so that the cache is in the right
375   // state.
376   service->AuditArtifacts({1, 2, 4});
377   EXPECT_FALSE(service->CacheInitialized());
378   EXPECT_FALSE(service->HasCaptureForTab(1));
379   EXPECT_FALSE(service->HasCaptureForTab(3));
380   task_environment()->RunUntilIdle();
381   task_environment()->AdvanceClock(base::TimeDelta::FromSeconds(10));
382   task_environment()->RunUntilIdle();
383 
384   EXPECT_TRUE(service->CacheInitialized());
385   EXPECT_TRUE(service->HasCaptureForTab(1));
386   EXPECT_FALSE(service->HasCaptureForTab(3));
387 }
388 
TEST_F(PaintPreviewTabServiceTest,EarlyCapture)389 TEST_F(PaintPreviewTabServiceTest, EarlyCapture) {
390   const int kTabId = 1U;
391 
392   MockPaintPreviewRecorder recorder;
393   recorder.SetResponse(mojom::PaintPreviewStatus::kOk);
394   OverrideInterface(&recorder);
395 
396   auto service = BuildServiceWithCache({});
397   service->CaptureTab(kTabId, web_contents(),
398                       base::BindOnce([](PaintPreviewTabService::Status status) {
399                         EXPECT_EQ(status, PaintPreviewTabService::Status::kOk);
400                       }));
401   EXPECT_FALSE(service->HasCaptureForTab(kTabId));
402   task_environment()->RunUntilIdle();
403 
404   EXPECT_TRUE(service->HasCaptureForTab(kTabId));
405 
406   auto file_manager = service->GetFileManager();
407   auto key = file_manager->CreateKey(kTabId);
408   service->GetTaskRunner()->PostTaskAndReplyWithResult(
409       FROM_HERE,
410       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
411       base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
412   task_environment()->RunUntilIdle();
413 
414   service->TabClosed(kTabId);
415   EXPECT_FALSE(service->HasCaptureForTab(kTabId));
416   service->GetTaskRunner()->PostTaskAndReplyWithResult(
417       FROM_HERE,
418       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
419       base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
420   task_environment()->RunUntilIdle();
421 }
422 
TEST_F(PaintPreviewTabServiceTest,CaptureTabAndCleanup)423 TEST_F(PaintPreviewTabServiceTest, CaptureTabAndCleanup) {
424   const int kTabId = 1U;
425 
426   MockPaintPreviewRecorder recorder;
427   recorder.SetResponse(mojom::PaintPreviewStatus::kOk);
428   OverrideInterface(&recorder);
429 
430   auto service = BuildServiceWithCache({kTabId + 1});
431   task_environment()->RunUntilIdle();
432   EXPECT_TRUE(service->CacheInitialized());
433   base::FilePath old_path = GetPath()
434                                 .AppendASCII("paint_preview")
435                                 .AppendASCII(kFeatureName)
436                                 .AppendASCII(base::NumberToString(kTabId + 1));
437   // The threshold for cleanup is 25 MB.
438   std::string data(25 * 1000 * 1000, 'x');
439   EXPECT_TRUE(base::WriteFile(old_path.AppendASCII("foo.txt"), data.data(),
440                               data.size()));
441   EXPECT_TRUE(base::PathExists(old_path));
442   EXPECT_TRUE(service->HasCaptureForTab(kTabId + 1));
443 
444   service->CaptureTab(kTabId, web_contents(),
445                       base::BindOnce([](PaintPreviewTabService::Status status) {
446                         EXPECT_EQ(status, PaintPreviewTabService::Status::kOk);
447                       }));
448   task_environment()->RunUntilIdle();
449   EXPECT_TRUE(service->HasCaptureForTab(kTabId));
450 
451   auto file_manager = service->GetFileManager();
452   auto key = file_manager->CreateKey(kTabId);
453   service->GetTaskRunner()->PostTaskAndReplyWithResult(
454       FROM_HERE,
455       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
456       base::BindOnce([](bool exists) { EXPECT_TRUE(exists); }));
457   task_environment()->RunUntilIdle();
458   EXPECT_FALSE(base::PathExists(old_path));
459   EXPECT_FALSE(service->HasCaptureForTab(kTabId + 1));
460   EXPECT_TRUE(service->HasCaptureForTab(kTabId));
461 
462   service->TabClosed(kTabId);
463   EXPECT_FALSE(service->HasCaptureForTab(kTabId));
464   task_environment()->RunUntilIdle();
465   service->GetTaskRunner()->PostTaskAndReplyWithResult(
466       FROM_HERE,
467       base::BindOnce(&FileManager::DirectoryExists, file_manager, key),
468       base::BindOnce([](bool exists) { EXPECT_FALSE(exists); }));
469   task_environment()->RunUntilIdle();
470 }
471 
472 }  // namespace paint_preview
473