Home | History | Annotate | Download | only in service_worker
      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 
     26 namespace content {
     27 
     28 namespace {
     29 const char kTestData[] = "Hello World";
     30 
     31 // Returns a BlobProtocolHandler that uses |blob_storage_context|. Caller owns
     32 // the memory.
     33 storage::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 
     43 class 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 
    266 class ServiceWorkerCacheTestP : public ServiceWorkerCacheTest,
    267                                 public testing::WithParamInterface<bool> {
    268   virtual bool MemoryOnly() OVERRIDE { return !GetParam(); }
    269 };
    270 
    271 TEST_P(ServiceWorkerCacheTestP, PutNoBody) {
    272   EXPECT_TRUE(Put(no_body_request_, no_body_response_));
    273 }
    274 
    275 TEST_P(ServiceWorkerCacheTestP, PutBody) {
    276   EXPECT_TRUE(Put(body_request_, body_response_));
    277 }
    278 
    279 TEST_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 
    294 TEST_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 
    303 TEST_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 
    315 TEST_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 
    328 TEST_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 
    337 TEST_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 
    345 TEST_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 
    359 TEST_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 
    369 TEST_P(ServiceWorkerCacheTestP, VaryStar) {
    370   body_response_.headers["vary"] = "*";
    371   EXPECT_TRUE(Put(body_request_, body_response_));
    372   EXPECT_FALSE(Match(body_request_));
    373 }
    374 
    375 TEST_P(ServiceWorkerCacheTestP, EmptyKeys) {
    376   EXPECT_TRUE(Keys());
    377   EXPECT_EQ(0u, callback_strings_.size());
    378 }
    379 
    380 TEST_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 
    391 TEST_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
    413 TEST_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 
    424 TEST_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 
    435 TEST_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 
    444 TEST_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 
    454 TEST_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 
    464 TEST_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 
    477 INSTANTIATE_TEST_CASE_P(ServiceWorkerCacheTest,
    478                         ServiceWorkerCacheTestP,
    479                         ::testing::Values(false, true));
    480 
    481 }  // namespace content
    482