1// Copyright 2014 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_cache.h" 6 7#include "base/files/file_path.h" 8#include "base/files/scoped_temp_dir.h" 9#include "base/message_loop/message_loop_proxy.h" 10#include "base/run_loop.h" 11#include "content/browser/fileapi/chrome_blob_storage_context.h" 12#include "content/browser/fileapi/mock_url_request_delegate.h" 13#include "content/common/service_worker/service_worker_types.h" 14#include "content/public/browser/browser_thread.h" 15#include "content/public/test/test_browser_context.h" 16#include "content/public/test/test_browser_thread_bundle.h" 17#include "net/url_request/url_request_context.h" 18#include "net/url_request/url_request_context_getter.h" 19#include "net/url_request/url_request_job_factory_impl.h" 20#include "storage/browser/blob/blob_data_handle.h" 21#include "storage/browser/blob/blob_storage_context.h" 22#include "storage/browser/blob/blob_url_request_job_factory.h" 23#include "storage/common/blob/blob_data.h" 24#include "testing/gtest/include/gtest/gtest.h" 25 26namespace content { 27 28namespace { 29const char kTestData[] = "Hello World"; 30 31// Returns a BlobProtocolHandler that uses |blob_storage_context|. Caller owns 32// the memory. 33storage::BlobProtocolHandler* CreateMockBlobProtocolHandler( 34 storage::BlobStorageContext* blob_storage_context) { 35 // The FileSystemContext and MessageLoopProxy are not actually used but a 36 // MessageLoopProxy is needed to avoid a DCHECK in BlobURLRequestJob ctor. 37 return new storage::BlobProtocolHandler( 38 blob_storage_context, NULL, base::MessageLoopProxy::current().get()); 39} 40 41} // namespace 42 43class ServiceWorkerCacheTest : public testing::Test { 44 public: 45 ServiceWorkerCacheTest() 46 : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), 47 callback_error_(ServiceWorkerCache::ErrorTypeOK) {} 48 49 virtual void SetUp() OVERRIDE { 50 ChromeBlobStorageContext* blob_storage_context = 51 ChromeBlobStorageContext::GetFor(&browser_context_); 52 // Wait for chrome_blob_storage_context to finish initializing. 53 base::RunLoop().RunUntilIdle(); 54 blob_storage_context_ = blob_storage_context->context(); 55 56 url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl); 57 url_request_job_factory_->SetProtocolHandler( 58 "blob", CreateMockBlobProtocolHandler(blob_storage_context->context())); 59 60 net::URLRequestContext* url_request_context = 61 browser_context_.GetRequestContext()->GetURLRequestContext(); 62 63 url_request_context->set_job_factory(url_request_job_factory_.get()); 64 65 CreateRequests(blob_storage_context); 66 67 if (MemoryOnly()) { 68 cache_ = ServiceWorkerCache::CreateMemoryCache( 69 url_request_context, 70 blob_storage_context->context()->AsWeakPtr()); 71 } else { 72 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 73 cache_ = ServiceWorkerCache::CreatePersistentCache( 74 temp_dir_.path(), 75 url_request_context, 76 blob_storage_context->context()->AsWeakPtr()); 77 } 78 } 79 80 virtual void TearDown() OVERRIDE { 81 base::RunLoop().RunUntilIdle(); 82 } 83 84 void CreateRequests(ChromeBlobStorageContext* blob_storage_context) { 85 ServiceWorkerHeaderMap headers; 86 headers.insert(std::make_pair("a", "a")); 87 headers.insert(std::make_pair("b", "b")); 88 body_request_ = ServiceWorkerFetchRequest( 89 GURL("http://example.com/body.html"), "GET", headers, GURL(""), false); 90 no_body_request_ = 91 ServiceWorkerFetchRequest(GURL("http://example.com/no_body.html"), 92 "GET", 93 headers, 94 GURL(""), 95 false); 96 97 std::string expected_response; 98 for (int i = 0; i < 100; ++i) 99 expected_blob_data_ += kTestData; 100 101 scoped_refptr<storage::BlobData> blob_data( 102 new storage::BlobData("blob-id:myblob")); 103 blob_data->AppendData(expected_blob_data_); 104 105 blob_handle_ = 106 blob_storage_context->context()->AddFinishedBlob(blob_data.get()); 107 108 body_response_ = ServiceWorkerResponse(GURL("http://example.com/body.html"), 109 200, 110 "OK", 111 headers, 112 blob_handle_->uuid()); 113 114 no_body_response_ = ServiceWorkerResponse( 115 GURL("http://example.com/no_body.html"), 200, "OK", headers, ""); 116 } 117 118 scoped_ptr<ServiceWorkerFetchRequest> CopyFetchRequest( 119 const ServiceWorkerFetchRequest& request) { 120 return make_scoped_ptr(new ServiceWorkerFetchRequest(request.url, 121 request.method, 122 request.headers, 123 request.referrer, 124 request.is_reload)); 125 } 126 127 scoped_ptr<ServiceWorkerResponse> CopyFetchResponse( 128 const ServiceWorkerResponse& response) { 129 return make_scoped_ptr(new ServiceWorkerResponse(response.url, 130 response.status_code, 131 response.status_text, 132 response.headers, 133 response.blob_uuid)); 134 } 135 136 bool Put(const ServiceWorkerFetchRequest& request, 137 const ServiceWorkerResponse& response) { 138 scoped_ptr<base::RunLoop> loop(new base::RunLoop()); 139 140 cache_->Put(CopyFetchRequest(request), 141 CopyFetchResponse(response), 142 base::Bind(&ServiceWorkerCacheTest::ErrorTypeCallback, 143 base::Unretained(this), 144 base::Unretained(loop.get()))); 145 // TODO(jkarlin): These functions should use base::RunLoop().RunUntilIdle() 146 // once the cache uses a passed in MessageLoopProxy instead of the CACHE 147 // thread. 148 loop->Run(); 149 150 return callback_error_ == ServiceWorkerCache::ErrorTypeOK; 151 } 152 153 bool Match(const ServiceWorkerFetchRequest& request) { 154 scoped_ptr<base::RunLoop> loop(new base::RunLoop()); 155 156 cache_->Match(CopyFetchRequest(request), 157 base::Bind(&ServiceWorkerCacheTest::ResponseAndErrorCallback, 158 base::Unretained(this), 159 base::Unretained(loop.get()))); 160 loop->Run(); 161 162 return callback_error_ == ServiceWorkerCache::ErrorTypeOK; 163 } 164 165 bool Delete(const ServiceWorkerFetchRequest& request) { 166 scoped_ptr<base::RunLoop> loop(new base::RunLoop()); 167 168 cache_->Delete(CopyFetchRequest(request), 169 base::Bind(&ServiceWorkerCacheTest::ErrorTypeCallback, 170 base::Unretained(this), 171 base::Unretained(loop.get()))); 172 loop->Run(); 173 174 return callback_error_ == ServiceWorkerCache::ErrorTypeOK; 175 } 176 177 bool Keys() { 178 scoped_ptr<base::RunLoop> loop(new base::RunLoop()); 179 180 cache_->Keys(base::Bind(&ServiceWorkerCacheTest::RequestsCallback, 181 base::Unretained(this), 182 base::Unretained(loop.get()))); 183 loop->Run(); 184 185 return callback_error_ == ServiceWorkerCache::ErrorTypeOK; 186 } 187 188 void RequestsCallback(base::RunLoop* run_loop, 189 ServiceWorkerCache::ErrorType error, 190 scoped_ptr<ServiceWorkerCache::Requests> requests) { 191 callback_error_ = error; 192 callback_strings_.clear(); 193 for (size_t i = 0u; i < requests->size(); ++i) 194 callback_strings_.push_back(requests->at(i).url.spec()); 195 run_loop->Quit(); 196 } 197 198 void ErrorTypeCallback(base::RunLoop* run_loop, 199 ServiceWorkerCache::ErrorType error) { 200 callback_error_ = error; 201 run_loop->Quit(); 202 } 203 204 void ResponseAndErrorCallback( 205 base::RunLoop* run_loop, 206 ServiceWorkerCache::ErrorType error, 207 scoped_ptr<ServiceWorkerResponse> response, 208 scoped_ptr<storage::BlobDataHandle> body_handle) { 209 callback_error_ = error; 210 callback_response_ = response.Pass(); 211 212 if (error == ServiceWorkerCache::ErrorTypeOK && 213 !callback_response_->blob_uuid.empty()) { 214 callback_response_data_ = body_handle.Pass(); 215 } 216 217 run_loop->Quit(); 218 } 219 220 void CopyBody(storage::BlobDataHandle* blob_handle, std::string* output) { 221 storage::BlobData* data = blob_handle->data(); 222 std::vector<storage::BlobData::Item> items = data->items(); 223 for (size_t i = 0, max = items.size(); i < max; ++i) 224 output->append(items[i].bytes(), items[i].length()); 225 } 226 227 bool VerifyKeys(const std::vector<std::string>& expected_keys) { 228 if (expected_keys.size() != callback_strings_.size()) 229 return false; 230 231 std::set<std::string> found_set; 232 for (int i = 0, max = callback_strings_.size(); i < max; ++i) 233 found_set.insert(callback_strings_[i]); 234 235 for (int i = 0, max = expected_keys.size(); i < max; ++i) { 236 if (found_set.find(expected_keys[i]) == found_set.end()) 237 return false; 238 } 239 return true; 240 } 241 242 virtual bool MemoryOnly() { return false; } 243 244 protected: 245 TestBrowserContext browser_context_; 246 TestBrowserThreadBundle browser_thread_bundle_; 247 scoped_ptr<net::URLRequestJobFactoryImpl> url_request_job_factory_; 248 storage::BlobStorageContext* blob_storage_context_; 249 250 base::ScopedTempDir temp_dir_; 251 scoped_refptr<ServiceWorkerCache> cache_; 252 253 ServiceWorkerFetchRequest body_request_; 254 ServiceWorkerResponse body_response_; 255 ServiceWorkerFetchRequest no_body_request_; 256 ServiceWorkerResponse no_body_response_; 257 scoped_ptr<storage::BlobDataHandle> blob_handle_; 258 std::string expected_blob_data_; 259 260 ServiceWorkerCache::ErrorType callback_error_; 261 scoped_ptr<ServiceWorkerResponse> callback_response_; 262 scoped_ptr<storage::BlobDataHandle> callback_response_data_; 263 std::vector<std::string> callback_strings_; 264}; 265 266class ServiceWorkerCacheTestP : public ServiceWorkerCacheTest, 267 public testing::WithParamInterface<bool> { 268 virtual bool MemoryOnly() OVERRIDE { return !GetParam(); } 269}; 270 271TEST_P(ServiceWorkerCacheTestP, PutNoBody) { 272 EXPECT_TRUE(Put(no_body_request_, no_body_response_)); 273} 274 275TEST_P(ServiceWorkerCacheTestP, PutBody) { 276 EXPECT_TRUE(Put(body_request_, body_response_)); 277} 278 279TEST_F(ServiceWorkerCacheTest, PutBodyDropBlobRef) { 280 scoped_ptr<base::RunLoop> loop(new base::RunLoop()); 281 cache_->Put(CopyFetchRequest(body_request_), 282 CopyFetchResponse(body_response_), 283 base::Bind(&ServiceWorkerCacheTestP::ErrorTypeCallback, 284 base::Unretained(this), 285 base::Unretained(loop.get()))); 286 // The handle should be held by the cache now so the deref here should be 287 // okay. 288 blob_handle_.reset(); 289 loop->Run(); 290 291 EXPECT_EQ(ServiceWorkerCache::ErrorTypeOK, callback_error_); 292} 293 294TEST_P(ServiceWorkerCacheTestP, MatchNoBody) { 295 EXPECT_TRUE(Put(no_body_request_, no_body_response_)); 296 EXPECT_TRUE(Match(no_body_request_)); 297 EXPECT_EQ(200, callback_response_->status_code); 298 EXPECT_STREQ("OK", callback_response_->status_text.c_str()); 299 EXPECT_STREQ("http://example.com/no_body.html", 300 callback_response_->url.spec().c_str()); 301} 302 303TEST_P(ServiceWorkerCacheTestP, MatchBody) { 304 EXPECT_TRUE(Put(body_request_, body_response_)); 305 EXPECT_TRUE(Match(body_request_)); 306 EXPECT_EQ(200, callback_response_->status_code); 307 EXPECT_STREQ("OK", callback_response_->status_text.c_str()); 308 EXPECT_STREQ("http://example.com/body.html", 309 callback_response_->url.spec().c_str()); 310 std::string response_body; 311 CopyBody(callback_response_data_.get(), &response_body); 312 EXPECT_STREQ(expected_blob_data_.c_str(), response_body.c_str()); 313} 314 315TEST_P(ServiceWorkerCacheTestP, Vary) { 316 body_request_.headers["vary_foo"] = "foo"; 317 body_response_.headers["vary"] = "vary_foo"; 318 EXPECT_TRUE(Put(body_request_, body_response_)); 319 EXPECT_TRUE(Match(body_request_)); 320 321 body_request_.headers["vary_foo"] = "bar"; 322 EXPECT_FALSE(Match(body_request_)); 323 324 body_request_.headers.erase("vary_foo"); 325 EXPECT_FALSE(Match(body_request_)); 326} 327 328TEST_P(ServiceWorkerCacheTestP, EmptyVary) { 329 body_response_.headers["vary"] = ""; 330 EXPECT_TRUE(Put(body_request_, body_response_)); 331 EXPECT_TRUE(Match(body_request_)); 332 333 body_request_.headers["zoo"] = "zoo"; 334 EXPECT_TRUE(Match(body_request_)); 335} 336 337TEST_P(ServiceWorkerCacheTestP, NoVaryButDiffHeaders) { 338 EXPECT_TRUE(Put(body_request_, body_response_)); 339 EXPECT_TRUE(Match(body_request_)); 340 341 body_request_.headers["zoo"] = "zoo"; 342 EXPECT_TRUE(Match(body_request_)); 343} 344 345TEST_P(ServiceWorkerCacheTestP, VaryMultiple) { 346 body_request_.headers["vary_foo"] = "foo"; 347 body_request_.headers["vary_bar"] = "bar"; 348 body_response_.headers["vary"] = " vary_foo , vary_bar"; 349 EXPECT_TRUE(Put(body_request_, body_response_)); 350 EXPECT_TRUE(Match(body_request_)); 351 352 body_request_.headers["vary_bar"] = "foo"; 353 EXPECT_FALSE(Match(body_request_)); 354 355 body_request_.headers.erase("vary_bar"); 356 EXPECT_FALSE(Match(body_request_)); 357} 358 359TEST_P(ServiceWorkerCacheTestP, VaryNewHeader) { 360 body_request_.headers["vary_foo"] = "foo"; 361 body_response_.headers["vary"] = " vary_foo, vary_bar"; 362 EXPECT_TRUE(Put(body_request_, body_response_)); 363 EXPECT_TRUE(Match(body_request_)); 364 365 body_request_.headers["vary_bar"] = "bar"; 366 EXPECT_FALSE(Match(body_request_)); 367} 368 369TEST_P(ServiceWorkerCacheTestP, VaryStar) { 370 body_response_.headers["vary"] = "*"; 371 EXPECT_TRUE(Put(body_request_, body_response_)); 372 EXPECT_FALSE(Match(body_request_)); 373} 374 375TEST_P(ServiceWorkerCacheTestP, EmptyKeys) { 376 EXPECT_TRUE(Keys()); 377 EXPECT_EQ(0u, callback_strings_.size()); 378} 379 380TEST_P(ServiceWorkerCacheTestP, TwoKeys) { 381 EXPECT_TRUE(Put(no_body_request_, no_body_response_)); 382 EXPECT_TRUE(Put(body_request_, body_response_)); 383 EXPECT_TRUE(Keys()); 384 EXPECT_EQ(2u, callback_strings_.size()); 385 std::vector<std::string> expected_keys; 386 expected_keys.push_back(no_body_request_.url.spec()); 387 expected_keys.push_back(body_request_.url.spec()); 388 EXPECT_TRUE(VerifyKeys(expected_keys)); 389} 390 391TEST_P(ServiceWorkerCacheTestP, TwoKeysThenOne) { 392 EXPECT_TRUE(Put(no_body_request_, no_body_response_)); 393 EXPECT_TRUE(Put(body_request_, body_response_)); 394 EXPECT_TRUE(Keys()); 395 EXPECT_EQ(2u, callback_strings_.size()); 396 std::vector<std::string> expected_keys; 397 expected_keys.push_back(no_body_request_.url.spec()); 398 expected_keys.push_back(body_request_.url.spec()); 399 EXPECT_TRUE(VerifyKeys(expected_keys)); 400 401 EXPECT_TRUE(Delete(body_request_)); 402 EXPECT_TRUE(Keys()); 403 EXPECT_EQ(1u, callback_strings_.size()); 404 std::vector<std::string> expected_key; 405 expected_key.push_back(no_body_request_.url.spec()); 406 EXPECT_TRUE(VerifyKeys(expected_key)); 407} 408 409// TODO(jkarlin): Once SimpleCache is working bug-free on Windows reenable these 410// tests. In the meanwhile we know that Windows operations will be a little 411// flaky (though not crashy). See https://crbug.com/409109 412#ifndef OS_WIN 413TEST_P(ServiceWorkerCacheTestP, DeleteNoBody) { 414 EXPECT_TRUE(Put(no_body_request_, no_body_response_)); 415 EXPECT_TRUE(Match(no_body_request_)); 416 EXPECT_TRUE(Delete(no_body_request_)); 417 EXPECT_FALSE(Match(no_body_request_)); 418 EXPECT_FALSE(Delete(no_body_request_)); 419 EXPECT_TRUE(Put(no_body_request_, no_body_response_)); 420 EXPECT_TRUE(Match(no_body_request_)); 421 EXPECT_TRUE(Delete(no_body_request_)); 422} 423 424TEST_P(ServiceWorkerCacheTestP, DeleteBody) { 425 EXPECT_TRUE(Put(body_request_, body_response_)); 426 EXPECT_TRUE(Match(body_request_)); 427 EXPECT_TRUE(Delete(body_request_)); 428 EXPECT_FALSE(Match(body_request_)); 429 EXPECT_FALSE(Delete(body_request_)); 430 EXPECT_TRUE(Put(body_request_, body_response_)); 431 EXPECT_TRUE(Match(body_request_)); 432 EXPECT_TRUE(Delete(body_request_)); 433} 434 435TEST_P(ServiceWorkerCacheTestP, QuickStressNoBody) { 436 for (int i = 0; i < 100; ++i) { 437 EXPECT_FALSE(Match(no_body_request_)); 438 EXPECT_TRUE(Put(no_body_request_, no_body_response_)); 439 EXPECT_TRUE(Match(no_body_request_)); 440 EXPECT_TRUE(Delete(no_body_request_)); 441 } 442} 443 444TEST_P(ServiceWorkerCacheTestP, QuickStressBody) { 445 for (int i = 0; i < 100; ++i) { 446 ASSERT_FALSE(Match(body_request_)); 447 ASSERT_TRUE(Put(body_request_, body_response_)); 448 ASSERT_TRUE(Match(body_request_)); 449 ASSERT_TRUE(Delete(body_request_)); 450 } 451} 452#endif // OS_WIN 453 454TEST_F(ServiceWorkerCacheTest, CaselessServiceWorkerResponseHeaders) { 455 // ServiceWorkerCache depends on ServiceWorkerResponse having caseless 456 // headers so that it can quickly lookup vary headers. 457 ServiceWorkerResponse response( 458 GURL("http://www.example.com"), 200, "OK", ServiceWorkerHeaderMap(), ""); 459 response.headers["content-type"] = "foo"; 460 response.headers["Content-Type"] = "bar"; 461 EXPECT_EQ("bar", response.headers["content-type"]); 462} 463 464TEST_F(ServiceWorkerCacheTest, CaselessServiceWorkerFetchRequestHeaders) { 465 // ServiceWorkerCache depends on ServiceWorkerFetchRequest having caseless 466 // headers so that it can quickly lookup vary headers. 467 ServiceWorkerFetchRequest request(GURL("http://www.example.com"), 468 "GET", 469 ServiceWorkerHeaderMap(), 470 GURL(""), 471 false); 472 request.headers["content-type"] = "foo"; 473 request.headers["Content-Type"] = "bar"; 474 EXPECT_EQ("bar", request.headers["content-type"]); 475} 476 477INSTANTIATE_TEST_CASE_P(ServiceWorkerCacheTest, 478 ServiceWorkerCacheTestP, 479 ::testing::Values(false, true)); 480 481} // namespace content 482