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