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 "content/browser/service_worker/service_worker_storage_control_impl.h"
6 
7 #include "base/containers/span.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/test/bind_test_util.h"
10 #include "base/threading/thread_task_runner_handle.h"
11 #include "content/browser/service_worker/service_worker_storage.h"
12 #include "content/public/test/browser_task_environment.h"
13 #include "content/public/test/test_utils.h"
14 #include "net/disk_cache/disk_cache.h"
15 #include "net/http/http_util.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "third_party/blink/public/mojom/service_worker/navigation_preload_state.mojom.h"
18 
19 namespace content {
20 
21 using DatabaseStatus = storage::mojom::ServiceWorkerDatabaseStatus;
22 using FindRegistrationResult =
23     storage::mojom::ServiceWorkerFindRegistrationResultPtr;
24 
25 namespace {
26 
WriteResponseHead(storage::mojom::ServiceWorkerResourceWriter * writer,network::mojom::URLResponseHeadPtr response_head)27 int WriteResponseHead(storage::mojom::ServiceWorkerResourceWriter* writer,
28                       network::mojom::URLResponseHeadPtr response_head) {
29   int return_value;
30   base::RunLoop loop;
31   writer->WriteResponseHead(std::move(response_head),
32                             base::BindLambdaForTesting([&](int result) {
33                               return_value = result;
34                               loop.Quit();
35                             }));
36   loop.Run();
37   return return_value;
38 }
39 
WriteResponseData(storage::mojom::ServiceWorkerResourceWriter * writer,mojo_base::BigBuffer data)40 int WriteResponseData(storage::mojom::ServiceWorkerResourceWriter* writer,
41                       mojo_base::BigBuffer data) {
42   int return_value;
43   base::RunLoop loop;
44   writer->WriteData(std::move(data),
45                     base::BindLambdaForTesting([&](int result) {
46                       return_value = result;
47                       loop.Quit();
48                     }));
49   loop.Run();
50   return return_value;
51 }
52 
53 }  // namespace
54 
55 class ServiceWorkerStorageControlImplTest : public testing::Test {
56  public:
ServiceWorkerStorageControlImplTest()57   ServiceWorkerStorageControlImplTest()
58       : task_environment_(BrowserTaskEnvironment::IO_MAINLOOP) {}
59 
SetUp()60   void SetUp() override {
61     ASSERT_TRUE(user_data_directory_.CreateUniqueTempDir());
62 
63     auto storage = ServiceWorkerStorage::Create(
64         user_data_directory_.GetPath(),
65         /*database_task_runner=*/base::ThreadTaskRunnerHandle::Get(),
66         /*quota_manager_proxy=*/nullptr);
67     storage_impl_ =
68         std::make_unique<ServiceWorkerStorageControlImpl>(std::move(storage));
69   }
70 
TearDown()71   void TearDown() override {
72     storage_impl_.reset();
73     disk_cache::FlushCacheThreadForTesting();
74     content::RunAllTasksUntilIdle();
75   }
76 
storage()77   storage::mojom::ServiceWorkerStorageControl* storage() {
78     return storage_impl_.get();
79   }
80 
LazyInitializeForTest()81   void LazyInitializeForTest() { storage_impl_->LazyInitializeForTest(); }
82 
FindRegistrationForClientUrl(const GURL & client_url)83   FindRegistrationResult FindRegistrationForClientUrl(const GURL& client_url) {
84     FindRegistrationResult return_value;
85     base::RunLoop loop;
86     storage()->FindRegistrationForClientUrl(
87         client_url,
88         base::BindLambdaForTesting([&](FindRegistrationResult result) {
89           return_value = result.Clone();
90           loop.Quit();
91         }));
92     loop.Run();
93     return return_value;
94   }
95 
FindRegistrationForScope(const GURL & scope)96   FindRegistrationResult FindRegistrationForScope(const GURL& scope) {
97     FindRegistrationResult return_value;
98     base::RunLoop loop;
99     storage()->FindRegistrationForScope(
100         scope, base::BindLambdaForTesting([&](FindRegistrationResult result) {
101           return_value = result.Clone();
102           loop.Quit();
103         }));
104     loop.Run();
105     return return_value;
106   }
107 
FindRegistrationForId(int64_t registration_id,const GURL & origin)108   FindRegistrationResult FindRegistrationForId(int64_t registration_id,
109                                                const GURL& origin) {
110     FindRegistrationResult return_value;
111     base::RunLoop loop;
112     storage()->FindRegistrationForId(
113         registration_id, origin,
114         base::BindLambdaForTesting([&](FindRegistrationResult result) {
115           return_value = result.Clone();
116           loop.Quit();
117         }));
118     loop.Run();
119     return return_value;
120   }
121 
StoreRegistration(storage::mojom::ServiceWorkerRegistrationDataPtr registration,std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> resources,DatabaseStatus & out_status)122   void StoreRegistration(
123       storage::mojom::ServiceWorkerRegistrationDataPtr registration,
124       std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> resources,
125       DatabaseStatus& out_status) {
126     base::RunLoop loop;
127     storage()->StoreRegistration(
128         std::move(registration), std::move(resources),
129         base::BindLambdaForTesting([&](DatabaseStatus status) {
130           out_status = status;
131           loop.Quit();
132         }));
133     loop.Run();
134   }
135 
DeleteRegistration(int64_t registration_id,const GURL & origin,DatabaseStatus & out_status,storage::mojom::ServiceWorkerStorageOriginState & out_origin_state)136   void DeleteRegistration(
137       int64_t registration_id,
138       const GURL& origin,
139       DatabaseStatus& out_status,
140       storage::mojom::ServiceWorkerStorageOriginState& out_origin_state) {
141     base::RunLoop loop;
142     storage()->DeleteRegistration(
143         registration_id, origin,
144         base::BindLambdaForTesting(
145             [&](DatabaseStatus status,
146                 storage::mojom::ServiceWorkerStorageOriginState origin_state) {
147               out_status = status;
148               out_origin_state = origin_state;
149               loop.Quit();
150             }));
151     loop.Run();
152   }
153 
GetNewResourceId()154   int64_t GetNewResourceId() {
155     int64_t return_value;
156     base::RunLoop loop;
157     storage()->GetNewResourceId(
158         base::BindLambdaForTesting([&](int64_t resource_id) {
159           return_value = resource_id;
160           loop.Quit();
161         }));
162     loop.Run();
163     return return_value;
164   }
165 
166   mojo::Remote<storage::mojom::ServiceWorkerResourceWriter>
CreateNewResourceWriter()167   CreateNewResourceWriter() {
168     mojo::Remote<storage::mojom::ServiceWorkerResourceWriter> writer;
169     storage()->CreateResourceWriter(GetNewResourceId(),
170                                     writer.BindNewPipeAndPassReceiver());
171     return writer;
172   }
173 
174  private:
175   base::ScopedTempDir user_data_directory_;
176   BrowserTaskEnvironment task_environment_;
177   std::unique_ptr<ServiceWorkerStorageControlImpl> storage_impl_;
178 };
179 
180 // Tests that FindRegistration methods don't find anything without having stored
181 // anything.
TEST_F(ServiceWorkerStorageControlImplTest,FindRegistration_NoRegistration)182 TEST_F(ServiceWorkerStorageControlImplTest, FindRegistration_NoRegistration) {
183   const GURL kScope("https://www.example.com/scope/");
184   const GURL kClientUrl("https://www.example.com/scope/document.html");
185   const int64_t kRegistrationId = 0;
186 
187   LazyInitializeForTest();
188 
189   {
190     FindRegistrationResult result = FindRegistrationForClientUrl(kClientUrl);
191     EXPECT_EQ(result->status, DatabaseStatus::kErrorNotFound);
192   }
193 
194   {
195     FindRegistrationResult result = FindRegistrationForScope(kScope);
196     EXPECT_EQ(result->status, DatabaseStatus::kErrorNotFound);
197   }
198 
199   {
200     FindRegistrationResult result =
201         FindRegistrationForId(kRegistrationId, kScope.GetOrigin());
202     EXPECT_EQ(result->status, DatabaseStatus::kErrorNotFound);
203   }
204 }
205 
206 // Tests that storing/finding/deleting a registration work.
TEST_F(ServiceWorkerStorageControlImplTest,StoreAndDeleteRegistration)207 TEST_F(ServiceWorkerStorageControlImplTest, StoreAndDeleteRegistration) {
208   const GURL kScope("https://www.example.com/scope/");
209   const GURL kScriptUrl("https://www.example.com/scope/sw.js");
210   const GURL kClientUrl("https://www.example.com/scope/document.html");
211   const int64_t kRegistrationId = 0;
212   const int64_t kScriptSize = 10;
213 
214   LazyInitializeForTest();
215 
216   // Create a registration data with a single resource.
217   std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> resources;
218   resources.push_back(storage::mojom::ServiceWorkerResourceRecord::New(
219       kRegistrationId, kScriptUrl, kScriptSize));
220 
221   auto data = storage::mojom::ServiceWorkerRegistrationData::New();
222   data->registration_id = kRegistrationId;
223   data->scope = kScope;
224   data->script = kScriptUrl;
225   data->navigation_preload_state = blink::mojom::NavigationPreloadState::New();
226 
227   int64_t resources_total_size_bytes = 0;
228   for (auto& resource : resources) {
229     resources_total_size_bytes += resource->size_bytes;
230   }
231   data->resources_total_size_bytes = resources_total_size_bytes;
232 
233   // Store the registration data.
234   {
235     DatabaseStatus status;
236     StoreRegistration(std::move(data), std::move(resources), status);
237     ASSERT_EQ(status, DatabaseStatus::kOk);
238   }
239 
240   // Find the registration. Find operations should succeed.
241   {
242     FindRegistrationResult result = FindRegistrationForClientUrl(kClientUrl);
243     ASSERT_EQ(result->status, DatabaseStatus::kOk);
244     EXPECT_EQ(result->registration->registration_id, kRegistrationId);
245     EXPECT_EQ(result->registration->scope, kScope);
246     EXPECT_EQ(result->registration->script, kScriptUrl);
247     EXPECT_EQ(result->registration->resources_total_size_bytes,
248               resources_total_size_bytes);
249     EXPECT_EQ(result->resources.size(), 1UL);
250 
251     result = FindRegistrationForScope(kScope);
252     EXPECT_EQ(result->status, DatabaseStatus::kOk);
253     result = FindRegistrationForId(kRegistrationId, kScope.GetOrigin());
254     EXPECT_EQ(result->status, DatabaseStatus::kOk);
255   }
256 
257   // Delete the registration.
258   {
259     DatabaseStatus status;
260     storage::mojom::ServiceWorkerStorageOriginState origin_state;
261     DeleteRegistration(kRegistrationId, kScope.GetOrigin(), status,
262                        origin_state);
263     ASSERT_EQ(status, DatabaseStatus::kOk);
264     EXPECT_EQ(origin_state,
265               storage::mojom::ServiceWorkerStorageOriginState::kDelete);
266   }
267 
268   // Try to find the deleted registration. These operation should result in
269   // kErrorNotFound.
270   {
271     FindRegistrationResult result = FindRegistrationForClientUrl(kClientUrl);
272     EXPECT_EQ(result->status, DatabaseStatus::kErrorNotFound);
273     result = FindRegistrationForScope(kScope);
274     EXPECT_EQ(result->status, DatabaseStatus::kErrorNotFound);
275     result = FindRegistrationForId(kRegistrationId, kScope.GetOrigin());
276     EXPECT_EQ(result->status, DatabaseStatus::kErrorNotFound);
277   }
278 }
279 
280 // Tests that writing a service worker script succeeds.
TEST_F(ServiceWorkerStorageControlImplTest,WriteResource)281 TEST_F(ServiceWorkerStorageControlImplTest, WriteResource) {
282   LazyInitializeForTest();
283 
284   mojo::Remote<storage::mojom::ServiceWorkerResourceWriter> writer =
285       CreateNewResourceWriter();
286 
287   // Write a response head.
288   {
289     auto response_head = network::mojom::URLResponseHead::New();
290     response_head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
291         net::HttpUtil::AssembleRawHeaders(
292             "HTTP/1.1 200 OK\n"
293             "Content-Type: application/javascript\n"));
294     response_head->headers->GetMimeType(&response_head->mime_type);
295 
296     int result = WriteResponseHead(writer.get(), std::move(response_head));
297     ASSERT_GT(result, 0);
298   }
299 
300   // Write content.
301   {
302     const std::string kData("/* script body */");
303     mojo_base::BigBuffer data(base::as_bytes(base::make_span(kData)));
304     int data_size = data.size();
305 
306     int result = WriteResponseData(writer.get(), std::move(data));
307     ASSERT_EQ(data_size, result);
308   }
309 
310   // TODO(crbug.com/1055677): Read the resource and check the response head and
311   // content.
312 }
313 
314 }  // namespace content
315