blob: 0fd82a15e697a373924f59e1fc4d177c81ec2cf3 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/service_worker/service_worker_storage.h"
#include <stdint.h>
#include <string>
#include <utility>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_disk_cache.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/common/service_worker/service_worker_status_code.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/content_client.h"
#include "content/public/common/origin_trial_policy.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "ipc/ipc_message.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
using net::IOBuffer;
using net::TestCompletionCallback;
using net::WrappedIOBuffer;
namespace content {
namespace {
typedef ServiceWorkerDatabase::RegistrationData RegistrationData;
typedef ServiceWorkerDatabase::ResourceRecord ResourceRecord;
// This is a sample public key for testing the API. The corresponding private
// key (use this to generate new samples for this test file) is:
//
// 0x83, 0x67, 0xf4, 0xcd, 0x2a, 0x1f, 0x0e, 0x04, 0x0d, 0x43, 0x13,
// 0x4c, 0x67, 0xc4, 0xf4, 0x28, 0xc9, 0x90, 0x15, 0x02, 0xe2, 0xba,
// 0xfd, 0xbb, 0xfa, 0xbc, 0x92, 0x76, 0x8a, 0x2c, 0x4b, 0xc7, 0x75,
// 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2, 0x9a,
// 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f, 0x64,
// 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0
const uint8_t kTestPublicKey[] = {
0x75, 0x10, 0xac, 0xf9, 0x3a, 0x1c, 0xb8, 0xa9, 0x28, 0x70, 0xd2,
0x9a, 0xd0, 0x0b, 0x59, 0xe1, 0xac, 0x2b, 0xb7, 0xd5, 0xca, 0x1f,
0x64, 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0,
};
void StatusAndQuitCallback(ServiceWorkerStatusCode* result,
const base::Closure& quit_closure,
ServiceWorkerStatusCode status) {
*result = status;
quit_closure.Run();
}
void StatusCallback(bool* was_called,
ServiceWorkerStatusCode* result,
ServiceWorkerStatusCode status) {
*was_called = true;
*result = status;
}
ServiceWorkerStorage::StatusCallback MakeStatusCallback(
bool* was_called,
ServiceWorkerStatusCode* result) {
return base::Bind(&StatusCallback, was_called, result);
}
void FindCallback(bool* was_called,
ServiceWorkerStatusCode* result,
scoped_refptr<ServiceWorkerRegistration>* found,
ServiceWorkerStatusCode status,
scoped_refptr<ServiceWorkerRegistration> registration) {
*was_called = true;
*result = status;
*found = std::move(registration);
}
ServiceWorkerStorage::FindRegistrationCallback MakeFindCallback(
bool* was_called,
ServiceWorkerStatusCode* result,
scoped_refptr<ServiceWorkerRegistration>* found) {
return base::Bind(&FindCallback, was_called, result, found);
}
void GetAllCallback(
bool* was_called,
ServiceWorkerStatusCode* result,
std::vector<scoped_refptr<ServiceWorkerRegistration>>* all_out,
ServiceWorkerStatusCode status,
const std::vector<scoped_refptr<ServiceWorkerRegistration>>& all) {
*was_called = true;
*result = status;
*all_out = all;
}
void GetAllInfosCallback(
bool* was_called,
ServiceWorkerStatusCode* result,
std::vector<ServiceWorkerRegistrationInfo>* all_out,
ServiceWorkerStatusCode status,
const std::vector<ServiceWorkerRegistrationInfo>& all) {
*was_called = true;
*result = status;
*all_out = all;
}
ServiceWorkerStorage::GetRegistrationsCallback MakeGetRegistrationsCallback(
bool* was_called,
ServiceWorkerStatusCode* status,
std::vector<scoped_refptr<ServiceWorkerRegistration>>* all) {
return base::Bind(&GetAllCallback, was_called, status, all);
}
ServiceWorkerStorage::GetRegistrationsInfosCallback
MakeGetRegistrationsInfosCallback(
bool* was_called,
ServiceWorkerStatusCode* status,
std::vector<ServiceWorkerRegistrationInfo>* all) {
return base::Bind(&GetAllInfosCallback, was_called, status, all);
}
void GetUserDataCallback(bool* was_called,
std::vector<std::string>* data_out,
ServiceWorkerStatusCode* status_out,
const std::vector<std::string>& data,
ServiceWorkerStatusCode status) {
*was_called = true;
*data_out = data;
*status_out = status;
}
void GetUserDataForAllRegistrationsCallback(
bool* was_called,
std::vector<std::pair<int64_t, std::string>>* data_out,
ServiceWorkerStatusCode* status_out,
const std::vector<std::pair<int64_t, std::string>>& data,
ServiceWorkerStatusCode status) {
*was_called = true;
*data_out = data;
*status_out = status;
}
int WriteResponse(ServiceWorkerStorage* storage,
int64_t id,
const std::string& headers,
IOBuffer* body,
int length) {
std::unique_ptr<ServiceWorkerResponseWriter> writer =
storage->CreateResponseWriter(id);
std::unique_ptr<net::HttpResponseInfo> info(new net::HttpResponseInfo);
info->request_time = base::Time::Now();
info->response_time = base::Time::Now();
info->was_cached = false;
info->headers = new net::HttpResponseHeaders(headers);
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
new HttpResponseInfoIOBuffer(info.release());
int rv = 0;
{
TestCompletionCallback cb;
writer->WriteInfo(info_buffer.get(), cb.callback());
rv = cb.WaitForResult();
if (rv < 0)
return rv;
}
{
TestCompletionCallback cb;
writer->WriteData(body, length, cb.callback());
rv = cb.WaitForResult();
}
return rv;
}
int WriteStringResponse(ServiceWorkerStorage* storage,
int64_t id,
const std::string& headers,
const std::string& body) {
scoped_refptr<IOBuffer> body_buffer(new WrappedIOBuffer(body.data()));
return WriteResponse(storage, id, headers, body_buffer.get(), body.length());
}
int WriteBasicResponse(ServiceWorkerStorage* storage, int64_t id) {
const char kHttpHeaders[] = "HTTP/1.0 200 HONKYDORY\0Content-Length: 5\0\0";
const char kHttpBody[] = "Hello";
std::string headers(kHttpHeaders, arraysize(kHttpHeaders));
return WriteStringResponse(storage, id, headers, std::string(kHttpBody));
}
int ReadResponseInfo(ServiceWorkerStorage* storage,
int64_t id,
HttpResponseInfoIOBuffer* info_buffer) {
std::unique_ptr<ServiceWorkerResponseReader> reader =
storage->CreateResponseReader(id);
TestCompletionCallback cb;
reader->ReadInfo(info_buffer, cb.callback());
return cb.WaitForResult();
}
bool VerifyBasicResponse(ServiceWorkerStorage* storage,
int64_t id,
bool expected_positive_result) {
const std::string kExpectedHttpBody("Hello");
std::unique_ptr<ServiceWorkerResponseReader> reader =
storage->CreateResponseReader(id);
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
new HttpResponseInfoIOBuffer();
int rv = ReadResponseInfo(storage, id, info_buffer.get());
if (expected_positive_result)
EXPECT_LT(0, rv);
if (rv <= 0)
return false;
std::string received_body;
const int kBigEnough = 512;
scoped_refptr<net::IOBuffer> buffer = new IOBuffer(kBigEnough);
TestCompletionCallback cb;
reader->ReadData(buffer.get(), kBigEnough, cb.callback());
rv = cb.WaitForResult();
EXPECT_EQ(static_cast<int>(kExpectedHttpBody.size()), rv);
if (rv <= 0)
return false;
received_body.assign(buffer->data(), rv);
bool status_match =
std::string("HONKYDORY") ==
info_buffer->http_info->headers->GetStatusText();
bool data_match = kExpectedHttpBody == received_body;
EXPECT_TRUE(status_match);
EXPECT_TRUE(data_match);
return status_match && data_match;
}
int WriteResponseMetadata(ServiceWorkerStorage* storage,
int64_t id,
const std::string& metadata) {
scoped_refptr<IOBuffer> body_buffer(new WrappedIOBuffer(metadata.data()));
std::unique_ptr<ServiceWorkerResponseMetadataWriter> metadata_writer =
storage->CreateResponseMetadataWriter(id);
TestCompletionCallback cb;
metadata_writer->WriteMetadata(body_buffer.get(), metadata.length(),
cb.callback());
return cb.WaitForResult();
}
int WriteMetadata(ServiceWorkerVersion* version,
const GURL& url,
const std::string& metadata) {
const std::vector<char> data(metadata.begin(), metadata.end());
EXPECT_TRUE(version);
TestCompletionCallback cb;
version->script_cache_map()->WriteMetadata(url, data, cb.callback());
return cb.WaitForResult();
}
int ClearMetadata(ServiceWorkerVersion* version, const GURL& url) {
EXPECT_TRUE(version);
TestCompletionCallback cb;
version->script_cache_map()->ClearMetadata(url, cb.callback());
return cb.WaitForResult();
}
bool VerifyResponseMetadata(ServiceWorkerStorage* storage,
int64_t id,
const std::string& expected_metadata) {
std::unique_ptr<ServiceWorkerResponseReader> reader =
storage->CreateResponseReader(id);
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
new HttpResponseInfoIOBuffer();
{
TestCompletionCallback cb;
reader->ReadInfo(info_buffer.get(), cb.callback());
int rv = cb.WaitForResult();
EXPECT_LT(0, rv);
}
const net::HttpResponseInfo* read_head = info_buffer->http_info.get();
if (!read_head->metadata.get())
return false;
EXPECT_EQ(0, memcmp(expected_metadata.data(), read_head->metadata->data(),
expected_metadata.length()));
return true;
}
} // namespace
class ServiceWorkerStorageTest : public testing::Test {
public:
ServiceWorkerStorageTest()
: browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {
}
void SetUp() override { InitializeTestHelper(); }
void TearDown() override {
helper_.reset();
base::RunLoop().RunUntilIdle();
}
bool InitUserDataDirectory() {
if (!user_data_directory_.CreateUniqueTempDir())
return false;
user_data_directory_path_ = user_data_directory_.GetPath();
return true;
}
void InitializeTestHelper() {
helper_.reset(new EmbeddedWorkerTestHelper(user_data_directory_path_));
base::RunLoop().RunUntilIdle();
}
ServiceWorkerContextCore* context() { return helper_->context(); }
ServiceWorkerStorage* storage() { return helper_->context()->storage(); }
// A static class method for friendliness.
static void VerifyPurgeableListStatusCallback(
ServiceWorkerDatabase* database,
std::set<int64_t>* purgeable_ids,
bool* was_called,
ServiceWorkerStatusCode* result,
ServiceWorkerStatusCode status) {
*was_called = true;
*result = status;
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
database->GetPurgeableResourceIds(purgeable_ids));
}
protected:
void LazyInitialize() {
storage()->LazyInitialize(base::Bind(&base::DoNothing));
base::RunLoop().RunUntilIdle();
}
ServiceWorkerStatusCode StoreRegistration(
scoped_refptr<ServiceWorkerRegistration> registration,
scoped_refptr<ServiceWorkerVersion> version) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->StoreRegistration(registration.get(),
version.get(),
MakeStatusCallback(&was_called, &result));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode DeleteRegistration(int64_t registration_id,
const GURL& origin) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->DeleteRegistration(
registration_id, origin, MakeStatusCallback(&was_called, &result));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode GetAllRegistrationsInfos(
std::vector<ServiceWorkerRegistrationInfo>* registrations) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->GetAllRegistrationsInfos(
MakeGetRegistrationsInfosCallback(&was_called, &result, registrations));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode GetRegistrationsForOrigin(
const GURL& origin,
std::vector<scoped_refptr<ServiceWorkerRegistration>>* registrations) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->GetRegistrationsForOrigin(
origin,
MakeGetRegistrationsCallback(&was_called, &result, registrations));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode GetUserData(int64_t registration_id,
const std::vector<std::string>& keys,
std::vector<std::string>* data) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->GetUserData(
registration_id, keys,
base::Bind(&GetUserDataCallback, &was_called, data, &result));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode StoreUserData(
int64_t registration_id,
const GURL& origin,
const std::vector<std::pair<std::string, std::string>>& key_value_pairs) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->StoreUserData(registration_id, origin, key_value_pairs,
MakeStatusCallback(&was_called, &result));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode ClearUserData(int64_t registration_id,
const std::vector<std::string>& keys) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->ClearUserData(registration_id, keys,
MakeStatusCallback(&was_called, &result));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode GetUserDataForAllRegistrations(
const std::string& key,
std::vector<std::pair<int64_t, std::string>>* data) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->GetUserDataForAllRegistrations(
key, base::Bind(&GetUserDataForAllRegistrationsCallback, &was_called,
data, &result));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode UpdateToActiveState(
scoped_refptr<ServiceWorkerRegistration> registration) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->UpdateToActiveState(registration.get(),
MakeStatusCallback(&was_called, &result));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
void UpdateLastUpdateCheckTime(
scoped_refptr<ServiceWorkerRegistration> registration) {
storage()->UpdateLastUpdateCheckTime(registration.get());
base::RunLoop().RunUntilIdle();
}
ServiceWorkerStatusCode FindRegistrationForDocument(
const GURL& document_url,
scoped_refptr<ServiceWorkerRegistration>* registration) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->FindRegistrationForDocument(
document_url, MakeFindCallback(&was_called, &result, registration));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode FindRegistrationForPattern(
const GURL& scope,
scoped_refptr<ServiceWorkerRegistration>* registration) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->FindRegistrationForPattern(
scope, MakeFindCallback(&was_called, &result, registration));
EXPECT_FALSE(was_called); // always async
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode FindRegistrationForId(
int64_t registration_id,
const GURL& origin,
scoped_refptr<ServiceWorkerRegistration>* registration) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->FindRegistrationForId(
registration_id, origin,
MakeFindCallback(&was_called, &result, registration));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
ServiceWorkerStatusCode FindRegistrationForIdOnly(
int64_t registration_id,
scoped_refptr<ServiceWorkerRegistration>* registration) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->FindRegistrationForIdOnly(
registration_id, MakeFindCallback(&was_called, &result, registration));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_called);
return result;
}
// Directly writes a registration using
// ServiceWorkerDatabase::WriteRegistration rather than
// ServiceWorkerStorage::StoreRegistration. Useful for simulating a
// registration written by an earlier version of Chrome.
void WriteRegistration(const RegistrationData& registration,
const std::vector<ResourceRecord>& resources) {
ServiceWorkerDatabase::RegistrationData deleted_version;
std::vector<int64_t> newly_purgeable_resources;
ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->WriteRegistration(
registration, resources, &deleted_version,
&newly_purgeable_resources));
}
// user_data_directory_ must be declared first to preserve destructor order.
base::ScopedTempDir user_data_directory_;
base::FilePath user_data_directory_path_;
std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
TestBrowserThreadBundle browser_thread_bundle_;
};
TEST_F(ServiceWorkerStorageTest, DisabledStorage) {
const GURL kScope("http://www.example.com/scope/");
const GURL kScript("http://www.example.com/script.js");
const GURL kDocumentUrl("http://www.example.com/scope/document.html");
const int64_t kRegistrationId = 0;
const int64_t kVersionId = 0;
const int64_t kResourceId = 0;
LazyInitialize();
storage()->Disable();
scoped_refptr<ServiceWorkerRegistration> found_registration;
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
FindRegistrationForPattern(kScope, &found_registration));
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
FindRegistrationForId(kRegistrationId, kScope.GetOrigin(),
&found_registration));
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
FindRegistrationForIdOnly(kRegistrationId, &found_registration));
EXPECT_FALSE(storage()->GetUninstallingRegistration(kScope.GetOrigin()));
std::vector<scoped_refptr<ServiceWorkerRegistration>> found_registrations;
EXPECT_EQ(
SERVICE_WORKER_ERROR_ABORT,
GetRegistrationsForOrigin(kScope.GetOrigin(), &found_registrations));
std::vector<ServiceWorkerRegistrationInfo> all_registrations;
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
GetAllRegistrationsInfos(&all_registrations));
scoped_refptr<ServiceWorkerRegistration> live_registration =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
live_registration.get(), kScript, kVersionId, context()->AsWeakPtr());
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
StoreRegistration(live_registration, live_version));
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT, UpdateToActiveState(live_registration));
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
DeleteRegistration(kRegistrationId, kScope.GetOrigin()));
// Response reader and writer created by the disabled storage should fail to
// access the disk cache.
scoped_refptr<HttpResponseInfoIOBuffer> info_buffer =
new HttpResponseInfoIOBuffer();
EXPECT_EQ(net::ERR_CACHE_MISS,
ReadResponseInfo(storage(), kResourceId, info_buffer.get()));
EXPECT_EQ(net::ERR_FAILED, WriteBasicResponse(storage(), kResourceId));
EXPECT_EQ(net::ERR_FAILED,
WriteResponseMetadata(storage(), kResourceId, "foo"));
const std::string kUserDataKey = "key";
std::vector<std::string> user_data_out;
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
GetUserData(kRegistrationId, {kUserDataKey}, &user_data_out));
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
StoreUserData(kRegistrationId, kScope.GetOrigin(),
{{kUserDataKey, "foo"}}));
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
ClearUserData(kRegistrationId, {kUserDataKey}));
std::vector<std::pair<int64_t, std::string>> data_list_out;
EXPECT_EQ(SERVICE_WORKER_ERROR_ABORT,
GetUserDataForAllRegistrations(kUserDataKey, &data_list_out));
EXPECT_FALSE(
storage()->OriginHasForeignFetchRegistrations(kScope.GetOrigin()));
// Next available ids should be invalid.
EXPECT_EQ(kInvalidServiceWorkerRegistrationId,
storage()->NewRegistrationId());
EXPECT_EQ(kInvalidServiceWorkerVersionId, storage()->NewVersionId());
EXPECT_EQ(kInvalidServiceWorkerResourceId, storage()->NewRegistrationId());
}
TEST_F(ServiceWorkerStorageTest, StoreFindUpdateDeleteRegistration) {
const GURL kScope("http://www.test.not/scope/");
const GURL kDocumentUrl("http://www.test.not/scope/document.html");
const GURL kResource1("http://www.test.not/scope/resource1.js");
const int64_t kResource1Size = 1591234;
const GURL kResource2("http://www.test.not/scope/resource2.js");
const int64_t kResource2Size = 51;
const int64_t kRegistrationId = 0;
const int64_t kVersionId = 0;
const GURL kForeignFetchScope("http://www.test.not/scope/ff/");
const url::Origin kForeignFetchOrigin(GURL("https://example.com/"));
const base::Time kToday = base::Time::Now();
const base::Time kYesterday = kToday - base::TimeDelta::FromDays(1);
std::set<uint32_t> used_features = {124, 901, 1019};
scoped_refptr<ServiceWorkerRegistration> found_registration;
// We shouldn't find anything without having stored anything.
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForPattern(kScope, &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForId(
kRegistrationId, kScope.GetOrigin(), &found_registration));
EXPECT_FALSE(found_registration.get());
std::vector<ServiceWorkerDatabase::ResourceRecord> resources;
resources.push_back(
ServiceWorkerDatabase::ResourceRecord(1, kResource1, kResource1Size));
resources.push_back(
ServiceWorkerDatabase::ResourceRecord(2, kResource2, kResource2Size));
// Store something.
scoped_refptr<ServiceWorkerRegistration> live_registration =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
live_registration.get(), kResource1, kVersionId, context()->AsWeakPtr());
live_version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
live_version->SetStatus(ServiceWorkerVersion::INSTALLED);
live_version->script_cache_map()->SetResources(resources);
live_version->set_foreign_fetch_scopes(
std::vector<GURL>(1, kForeignFetchScope));
live_version->set_foreign_fetch_origins(
std::vector<url::Origin>(1, kForeignFetchOrigin));
live_version->set_used_features(used_features);
live_registration->SetWaitingVersion(live_version);
live_registration->set_last_update_check(kYesterday);
EXPECT_EQ(SERVICE_WORKER_OK,
StoreRegistration(live_registration, live_version));
// Now we should find it and get the live ptr back immediately.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
EXPECT_EQ(live_registration, found_registration);
EXPECT_EQ(kResource1Size + kResource2Size,
live_registration->resources_total_size_bytes());
EXPECT_EQ(kResource1Size + kResource2Size,
found_registration->resources_total_size_bytes());
EXPECT_EQ(used_features,
found_registration->waiting_version()->used_features());
found_registration = NULL;
// But FindRegistrationForPattern is always async.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForPattern(kScope, &found_registration));
EXPECT_EQ(live_registration, found_registration);
found_registration = NULL;
// Can be found by id too.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForId(
kRegistrationId, kScope.GetOrigin(), &found_registration));
ASSERT_TRUE(found_registration.get());
EXPECT_EQ(kRegistrationId, found_registration->id());
EXPECT_EQ(live_registration, found_registration);
found_registration = NULL;
// Can be found by just the id too.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForIdOnly(kRegistrationId, &found_registration));
ASSERT_TRUE(found_registration.get());
EXPECT_EQ(kRegistrationId, found_registration->id());
EXPECT_EQ(live_registration, found_registration);
found_registration = NULL;
// Drop the live registration, but keep the version live.
live_registration = NULL;
// Now FindRegistrationForDocument should be async.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
ASSERT_TRUE(found_registration.get());
EXPECT_EQ(kRegistrationId, found_registration->id());
EXPECT_TRUE(found_registration->HasOneRef());
// Check that sizes are populated correctly
EXPECT_EQ(live_version.get(), found_registration->waiting_version());
EXPECT_EQ(kResource1Size + kResource2Size,
found_registration->resources_total_size_bytes());
std::vector<ServiceWorkerRegistrationInfo> all_registrations;
EXPECT_EQ(SERVICE_WORKER_OK, GetAllRegistrationsInfos(&all_registrations));
EXPECT_EQ(1u, all_registrations.size());
ServiceWorkerRegistrationInfo info = all_registrations[0];
EXPECT_EQ(kResource1Size + kResource2Size, info.stored_version_size_bytes);
all_registrations.clear();
// Finding by origin should provide the same result if origin is kScope.
std::vector<scoped_refptr<ServiceWorkerRegistration>>
registrations_for_origin;
EXPECT_EQ(
SERVICE_WORKER_OK,
GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
EXPECT_EQ(1u, registrations_for_origin.size());
registrations_for_origin.clear();
EXPECT_EQ(SERVICE_WORKER_OK,
GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
&registrations_for_origin));
EXPECT_TRUE(registrations_for_origin.empty());
found_registration = NULL;
// Drop the live version too.
live_version = NULL;
// And FindRegistrationForPattern is always async.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForPattern(kScope, &found_registration));
ASSERT_TRUE(found_registration.get());
EXPECT_EQ(kRegistrationId, found_registration->id());
EXPECT_TRUE(found_registration->HasOneRef());
EXPECT_FALSE(found_registration->active_version());
ASSERT_TRUE(found_registration->waiting_version());
EXPECT_EQ(kYesterday, found_registration->last_update_check());
EXPECT_EQ(ServiceWorkerVersion::INSTALLED,
found_registration->waiting_version()->status());
EXPECT_EQ(
1u, found_registration->waiting_version()->foreign_fetch_scopes().size());
EXPECT_EQ(kForeignFetchScope,
found_registration->waiting_version()->foreign_fetch_scopes()[0]);
EXPECT_EQ(
1u,
found_registration->waiting_version()->foreign_fetch_origins().size());
EXPECT_EQ(kForeignFetchOrigin,
found_registration->waiting_version()->foreign_fetch_origins()[0]);
// Update to active and update the last check time.
scoped_refptr<ServiceWorkerVersion> temp_version =
found_registration->waiting_version();
temp_version->SetStatus(ServiceWorkerVersion::ACTIVATED);
found_registration->SetActiveVersion(temp_version);
temp_version = NULL;
EXPECT_EQ(SERVICE_WORKER_OK, UpdateToActiveState(found_registration));
found_registration->set_last_update_check(kToday);
UpdateLastUpdateCheckTime(found_registration.get());
found_registration = NULL;
// Trying to update a unstored registration to active should fail.
scoped_refptr<ServiceWorkerRegistration> unstored_registration =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId + 1,
context()->AsWeakPtr());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
UpdateToActiveState(unstored_registration));
unstored_registration = NULL;
// The Find methods should return a registration with an active version
// and the expected update time.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
ASSERT_TRUE(found_registration.get());
EXPECT_EQ(kRegistrationId, found_registration->id());
EXPECT_TRUE(found_registration->HasOneRef());
EXPECT_FALSE(found_registration->waiting_version());
ASSERT_TRUE(found_registration->active_version());
EXPECT_EQ(ServiceWorkerVersion::ACTIVATED,
found_registration->active_version()->status());
EXPECT_EQ(kToday, found_registration->last_update_check());
// Delete from storage but with a instance still live.
EXPECT_TRUE(context()->GetLiveVersion(kRegistrationId));
EXPECT_EQ(SERVICE_WORKER_OK,
DeleteRegistration(kRegistrationId, kScope.GetOrigin()));
EXPECT_TRUE(context()->GetLiveVersion(kRegistrationId));
// Should no longer be found.
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForId(
kRegistrationId, kScope.GetOrigin(), &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForIdOnly(kRegistrationId, &found_registration));
EXPECT_FALSE(found_registration.get());
// Deleting an unstored registration should succeed.
EXPECT_EQ(SERVICE_WORKER_OK,
DeleteRegistration(kRegistrationId + 1, kScope.GetOrigin()));
}
TEST_F(ServiceWorkerStorageTest, InstallingRegistrationsAreFindable) {
const GURL kScope("http://www.test.not/scope/");
const GURL kScript("http://www.test.not/script.js");
const GURL kDocumentUrl("http://www.test.not/scope/document.html");
const int64_t kRegistrationId = 0;
const int64_t kVersionId = 0;
scoped_refptr<ServiceWorkerRegistration> found_registration;
// Create an unstored registration.
scoped_refptr<ServiceWorkerRegistration> live_registration =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
live_registration.get(), kScript, kVersionId, context()->AsWeakPtr());
live_version->SetStatus(ServiceWorkerVersion::INSTALLING);
live_registration->SetWaitingVersion(live_version);
// Should not be findable, including by GetAllRegistrationsInfos.
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForId(
kRegistrationId, kScope.GetOrigin(), &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForIdOnly(kRegistrationId, &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForPattern(kScope, &found_registration));
EXPECT_FALSE(found_registration.get());
std::vector<ServiceWorkerRegistrationInfo> all_registrations;
EXPECT_EQ(SERVICE_WORKER_OK, GetAllRegistrationsInfos(&all_registrations));
EXPECT_TRUE(all_registrations.empty());
std::vector<scoped_refptr<ServiceWorkerRegistration>>
registrations_for_origin;
EXPECT_EQ(
SERVICE_WORKER_OK,
GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
EXPECT_TRUE(registrations_for_origin.empty());
EXPECT_EQ(SERVICE_WORKER_OK,
GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
&registrations_for_origin));
EXPECT_TRUE(registrations_for_origin.empty());
// Notify storage of it being installed.
storage()->NotifyInstallingRegistration(live_registration.get());
// Now should be findable.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForId(
kRegistrationId, kScope.GetOrigin(), &found_registration));
EXPECT_EQ(live_registration, found_registration);
found_registration = NULL;
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForIdOnly(kRegistrationId, &found_registration));
EXPECT_EQ(live_registration, found_registration);
found_registration = NULL;
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
EXPECT_EQ(live_registration, found_registration);
found_registration = NULL;
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForPattern(kScope, &found_registration));
EXPECT_EQ(live_registration, found_registration);
found_registration = NULL;
EXPECT_EQ(SERVICE_WORKER_OK, GetAllRegistrationsInfos(&all_registrations));
EXPECT_EQ(1u, all_registrations.size());
all_registrations.clear();
// Finding by origin should provide the same result if origin is kScope.
EXPECT_EQ(
SERVICE_WORKER_OK,
GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
EXPECT_EQ(1u, registrations_for_origin.size());
registrations_for_origin.clear();
EXPECT_EQ(SERVICE_WORKER_OK,
GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
&registrations_for_origin));
EXPECT_TRUE(registrations_for_origin.empty());
// Notify storage of installation no longer happening.
storage()->NotifyDoneInstallingRegistration(
live_registration.get(), NULL, SERVICE_WORKER_OK);
// Once again, should not be findable.
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForId(
kRegistrationId, kScope.GetOrigin(), &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForIdOnly(kRegistrationId, &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
FindRegistrationForPattern(kScope, &found_registration));
EXPECT_FALSE(found_registration.get());
EXPECT_EQ(SERVICE_WORKER_OK, GetAllRegistrationsInfos(&all_registrations));
EXPECT_TRUE(all_registrations.empty());
EXPECT_EQ(
SERVICE_WORKER_OK,
GetRegistrationsForOrigin(kScope.GetOrigin(), &registrations_for_origin));
EXPECT_TRUE(registrations_for_origin.empty());
EXPECT_EQ(SERVICE_WORKER_OK,
GetRegistrationsForOrigin(GURL("http://example.com/").GetOrigin(),
&registrations_for_origin));
EXPECT_TRUE(registrations_for_origin.empty());
}
TEST_F(ServiceWorkerStorageTest, StoreUserData) {
const GURL kScope("http://www.test.not/scope/");
const GURL kScript("http://www.test.not/script.js");
const int64_t kRegistrationId = 0;
const int64_t kVersionId = 0;
LazyInitialize();
// Store a registration.
scoped_refptr<ServiceWorkerRegistration> live_registration =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
live_registration.get(), kScript, kVersionId, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(ServiceWorkerDatabase::ResourceRecord(
1, live_version->script_url(), 100));
live_version->script_cache_map()->SetResources(records);
live_version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
live_version->SetStatus(ServiceWorkerVersion::INSTALLED);
live_registration->SetWaitingVersion(live_version);
EXPECT_EQ(SERVICE_WORKER_OK,
StoreRegistration(live_registration, live_version));
// Store user data associated with the registration.
std::vector<std::string> data_out;
EXPECT_EQ(
SERVICE_WORKER_OK,
StoreUserData(kRegistrationId, kScope.GetOrigin(), {{"key", "data"}}));
EXPECT_EQ(SERVICE_WORKER_OK,
GetUserData(kRegistrationId, {"key"}, &data_out));
ASSERT_EQ(1u, data_out.size());
EXPECT_EQ("data", data_out[0]);
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"unknown_key"}, &data_out));
std::vector<std::pair<int64_t, std::string>> data_list_out;
EXPECT_EQ(SERVICE_WORKER_OK,
GetUserDataForAllRegistrations("key", &data_list_out));
ASSERT_EQ(1u, data_list_out.size());
EXPECT_EQ(kRegistrationId, data_list_out[0].first);
EXPECT_EQ("data", data_list_out[0].second);
data_list_out.clear();
EXPECT_EQ(SERVICE_WORKER_OK,
GetUserDataForAllRegistrations("unknown_key", &data_list_out));
EXPECT_EQ(0u, data_list_out.size());
EXPECT_EQ(SERVICE_WORKER_OK, ClearUserData(kRegistrationId, {"key"}));
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"key"}, &data_out));
// Write/overwrite multiple user data keys.
EXPECT_EQ(SERVICE_WORKER_OK,
StoreUserData(
kRegistrationId, kScope.GetOrigin(),
{{"key", "overwrite"}, {"key3", "data3"}, {"key4", "data4"}}));
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"key2"}, &data_out));
EXPECT_TRUE(data_out.empty());
EXPECT_EQ(SERVICE_WORKER_OK,
GetUserData(kRegistrationId, {"key", "key3", "key4"}, &data_out));
ASSERT_EQ(3u, data_out.size());
EXPECT_EQ("overwrite", data_out[0]);
EXPECT_EQ("data3", data_out[1]);
EXPECT_EQ("data4", data_out[2]);
// Multiple gets fail if one is not found.
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"key", "key2"}, &data_out));
EXPECT_TRUE(data_out.empty());
// Delete multiple user data keys, even if some are not found.
EXPECT_EQ(SERVICE_WORKER_OK,
ClearUserData(kRegistrationId, {"key", "key2", "key3"}));
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"key"}, &data_out));
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"key2"}, &data_out));
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"key3"}, &data_out));
EXPECT_EQ(SERVICE_WORKER_OK,
GetUserData(kRegistrationId, {"key4"}, &data_out));
ASSERT_EQ(1u, data_out.size());
EXPECT_EQ("data4", data_out[0]);
// User data should be deleted when the associated registration is deleted.
ASSERT_EQ(
SERVICE_WORKER_OK,
StoreUserData(kRegistrationId, kScope.GetOrigin(), {{"key", "data"}}));
ASSERT_EQ(SERVICE_WORKER_OK,
GetUserData(kRegistrationId, {"key"}, &data_out));
ASSERT_EQ(1u, data_out.size());
ASSERT_EQ("data", data_out[0]);
EXPECT_EQ(SERVICE_WORKER_OK,
DeleteRegistration(kRegistrationId, kScope.GetOrigin()));
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"key"}, &data_out));
data_list_out.clear();
EXPECT_EQ(SERVICE_WORKER_OK,
GetUserDataForAllRegistrations("key", &data_list_out));
EXPECT_EQ(0u, data_list_out.size());
// Data access with an invalid registration id should be failed.
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
StoreUserData(kInvalidServiceWorkerRegistrationId,
kScope.GetOrigin(), {{"key", "data"}}));
EXPECT_EQ(
SERVICE_WORKER_ERROR_FAILED,
GetUserData(kInvalidServiceWorkerRegistrationId, {"key"}, &data_out));
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
ClearUserData(kInvalidServiceWorkerRegistrationId, {"key"}));
// Data access with an empty key should be failed.
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
StoreUserData(kRegistrationId, kScope.GetOrigin(),
std::vector<std::pair<std::string, std::string>>()));
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
StoreUserData(kRegistrationId, kScope.GetOrigin(),
{{std::string(), "data"}}));
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
StoreUserData(kRegistrationId, kScope.GetOrigin(),
{{std::string(), "data"}, {"key", "data"}}));
EXPECT_EQ(
SERVICE_WORKER_ERROR_FAILED,
GetUserData(kRegistrationId, std::vector<std::string>(), &data_out));
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
GetUserData(kRegistrationId, {std::string()}, &data_out));
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
GetUserData(kRegistrationId, {std::string(), "key"}, &data_out));
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
ClearUserData(kRegistrationId, std::vector<std::string>()));
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
ClearUserData(kRegistrationId, {std::string()}));
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
ClearUserData(kRegistrationId, {std::string(), "key"}));
data_list_out.clear();
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED,
GetUserDataForAllRegistrations(std::string(), &data_list_out));
}
// The *_BeforeInitialize tests exercise the API before LazyInitialize() is
// called.
TEST_F(ServiceWorkerStorageTest, StoreUserData_BeforeInitialize) {
const int kRegistrationId = 0;
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
StoreUserData(kRegistrationId, GURL("https://example.com"),
{{"key", "data"}}));
}
TEST_F(ServiceWorkerStorageTest, GetUserData_BeforeInitialize) {
const int kRegistrationId = 0;
std::vector<std::string> data_out;
EXPECT_EQ(SERVICE_WORKER_ERROR_NOT_FOUND,
GetUserData(kRegistrationId, {"key"}, &data_out));
}
TEST_F(ServiceWorkerStorageTest, ClearUserData_BeforeInitialize) {
const int kRegistrationId = 0;
EXPECT_EQ(SERVICE_WORKER_OK, ClearUserData(kRegistrationId, {"key"}));
}
TEST_F(ServiceWorkerStorageTest,
GetUserDataForAllRegistrations_BeforeInitialize) {
std::vector<std::pair<int64_t, std::string>> data_list_out;
EXPECT_EQ(SERVICE_WORKER_OK,
GetUserDataForAllRegistrations("key", &data_list_out));
EXPECT_TRUE(data_list_out.empty());
}
class ServiceWorkerResourceStorageTest : public ServiceWorkerStorageTest {
public:
void SetUp() override {
ServiceWorkerStorageTest::SetUp();
LazyInitialize();
scope_ = GURL("http://www.test.not/scope/");
script_ = GURL("http://www.test.not/script.js");
import_ = GURL("http://www.test.not/import.js");
document_url_ = GURL("http://www.test.not/scope/document.html");
registration_id_ = storage()->NewRegistrationId();
version_id_ = storage()->NewVersionId();
resource_id1_ = storage()->NewResourceId();
resource_id2_ = storage()->NewResourceId();
resource_id1_size_ = 239193;
resource_id2_size_ = 59923;
// Cons up a new registration+version with two script resources.
RegistrationData data;
data.registration_id = registration_id_;
data.scope = scope_;
data.script = script_;
data.version_id = version_id_;
data.is_active = false;
std::vector<ResourceRecord> resources;
resources.push_back(
ResourceRecord(resource_id1_, script_, resource_id1_size_));
resources.push_back(
ResourceRecord(resource_id2_, import_, resource_id2_size_));
registration_ = storage()->GetOrCreateRegistration(data, resources);
registration_->waiting_version()->SetStatus(ServiceWorkerVersion::NEW);
// Add the resources ids to the uncommitted list.
storage()->StoreUncommittedResourceId(resource_id1_);
storage()->StoreUncommittedResourceId(resource_id2_);
base::RunLoop().RunUntilIdle();
std::set<int64_t> verify_ids;
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetUncommittedResourceIds(&verify_ids));
EXPECT_EQ(2u, verify_ids.size());
// And dump something in the disk cache for them.
WriteBasicResponse(storage(), resource_id1_);
WriteBasicResponse(storage(), resource_id2_);
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, true));
// Storing the registration/version should take the resources ids out
// of the uncommitted list.
EXPECT_EQ(
SERVICE_WORKER_OK,
StoreRegistration(registration_, registration_->waiting_version()));
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetUncommittedResourceIds(&verify_ids));
EXPECT_TRUE(verify_ids.empty());
}
protected:
GURL scope_;
GURL script_;
GURL import_;
GURL document_url_;
int64_t registration_id_;
int64_t version_id_;
int64_t resource_id1_;
uint64_t resource_id1_size_;
int64_t resource_id2_;
uint64_t resource_id2_size_;
scoped_refptr<ServiceWorkerRegistration> registration_;
};
class ServiceWorkerResourceStorageDiskTest
: public ServiceWorkerResourceStorageTest {
public:
void SetUp() override {
ASSERT_TRUE(InitUserDataDirectory());
ServiceWorkerResourceStorageTest::SetUp();
}
};
TEST_F(ServiceWorkerResourceStorageTest,
WriteMetadataWithServiceWorkerResponseMetadataWriter) {
const char kMetadata1[] = "Test metadata";
const char kMetadata2[] = "small";
int64_t new_resource_id_ = storage()->NewResourceId();
// Writing metadata to nonexistent resoirce ID must fail.
EXPECT_GE(0, WriteResponseMetadata(storage(), new_resource_id_, kMetadata1));
// Check metadata is written.
EXPECT_EQ(static_cast<int>(strlen(kMetadata1)),
WriteResponseMetadata(storage(), resource_id1_, kMetadata1));
EXPECT_TRUE(VerifyResponseMetadata(storage(), resource_id1_, kMetadata1));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
// Check metadata is written and truncated.
EXPECT_EQ(static_cast<int>(strlen(kMetadata2)),
WriteResponseMetadata(storage(), resource_id1_, kMetadata2));
EXPECT_TRUE(VerifyResponseMetadata(storage(), resource_id1_, kMetadata2));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
// Check metadata is deleted.
EXPECT_EQ(0, WriteResponseMetadata(storage(), resource_id1_, ""));
EXPECT_FALSE(VerifyResponseMetadata(storage(), resource_id1_, ""));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
}
TEST_F(ServiceWorkerResourceStorageTest,
WriteMetadataWithServiceWorkerScriptCacheMap) {
const char kMetadata1[] = "Test metadata";
const char kMetadata2[] = "small";
ServiceWorkerVersion* version = registration_->waiting_version();
EXPECT_TRUE(version);
// Writing metadata to nonexistent URL must fail.
EXPECT_GE(0,
WriteMetadata(version, GURL("http://www.test.not/nonexistent.js"),
kMetadata1));
// Clearing metadata of nonexistent URL must fail.
EXPECT_GE(0,
ClearMetadata(version, GURL("http://www.test.not/nonexistent.js")));
// Check metadata is written.
EXPECT_EQ(static_cast<int>(strlen(kMetadata1)),
WriteMetadata(version, script_, kMetadata1));
EXPECT_TRUE(VerifyResponseMetadata(storage(), resource_id1_, kMetadata1));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
// Check metadata is written and truncated.
EXPECT_EQ(static_cast<int>(strlen(kMetadata2)),
WriteMetadata(version, script_, kMetadata2));
EXPECT_TRUE(VerifyResponseMetadata(storage(), resource_id1_, kMetadata2));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
// Check metadata is deleted.
EXPECT_EQ(0, ClearMetadata(version, script_));
EXPECT_FALSE(VerifyResponseMetadata(storage(), resource_id1_, ""));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
}
TEST_F(ServiceWorkerResourceStorageTest, DeleteRegistration_NoLiveVersion) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
std::set<int64_t> verify_ids;
registration_->SetWaitingVersion(NULL);
registration_ = NULL;
// Deleting the registration should result in the resources being added to the
// purgeable list and then doomed in the disk cache and removed from that
// list.
storage()->DeleteRegistration(
registration_id_,
scope_.GetOrigin(),
base::Bind(&VerifyPurgeableListStatusCallback,
base::Unretained(storage()->database_.get()),
&verify_ids,
&was_called,
&result));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_OK, result);
EXPECT_EQ(2u, verify_ids.size());
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_TRUE(verify_ids.empty());
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
}
TEST_F(ServiceWorkerResourceStorageTest, DeleteRegistration_WaitingVersion) {
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
std::set<int64_t> verify_ids;
// Deleting the registration should result in the resources being added to the
// purgeable list and then doomed in the disk cache and removed from that
// list.
storage()->DeleteRegistration(
registration_->id(),
scope_.GetOrigin(),
base::Bind(&VerifyPurgeableListStatusCallback,
base::Unretained(storage()->database_.get()),
&verify_ids,
&was_called,
&result));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_OK, result);
EXPECT_EQ(2u, verify_ids.size());
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_EQ(2u, verify_ids.size());
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, false));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, false));
// Doom the version, now it happens.
registration_->waiting_version()->Doom();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(SERVICE_WORKER_OK, result);
EXPECT_EQ(2u, verify_ids.size());
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_TRUE(verify_ids.empty());
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
}
TEST_F(ServiceWorkerResourceStorageTest, DeleteRegistration_ActiveVersion) {
// Promote the worker to active and add a controllee.
registration_->SetActiveVersion(registration_->waiting_version());
storage()->UpdateToActiveState(
registration_.get(), base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
std::unique_ptr<ServiceWorkerProviderHost> host = CreateProviderHostForWindow(
33 /* dummy render process id */, 1 /* dummy provider_id */,
true /* is_parent_frame_secure */, context()->AsWeakPtr(),
&remote_endpoint);
registration_->active_version()->AddControllee(host.get());
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
std::set<int64_t> verify_ids;
// Deleting the registration should move the resources to the purgeable list
// but keep them available.
storage()->DeleteRegistration(
registration_->id(),
scope_.GetOrigin(),
base::Bind(&VerifyPurgeableListStatusCallback,
base::Unretained(storage()->database_.get()),
&verify_ids,
&was_called,
&result));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_OK, result);
EXPECT_EQ(2u, verify_ids.size());
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_EQ(2u, verify_ids.size());
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, true));
// Removing the controllee should cause the resources to be deleted.
registration_->active_version()->RemoveControllee(host.get());
registration_->active_version()->Doom();
base::RunLoop().RunUntilIdle();
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_TRUE(verify_ids.empty());
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
}
TEST_F(ServiceWorkerResourceStorageDiskTest, CleanupOnRestart) {
// Promote the worker to active and add a controllee.
registration_->SetActiveVersion(registration_->waiting_version());
registration_->SetWaitingVersion(NULL);
storage()->UpdateToActiveState(
registration_.get(), base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
std::unique_ptr<ServiceWorkerProviderHost> host = CreateProviderHostForWindow(
33 /* dummy render process id */, 1 /* dummy provider_id */,
true /* is_parent_frame_secure */, context()->AsWeakPtr(),
&remote_endpoint);
registration_->active_version()->AddControllee(host.get());
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
std::set<int64_t> verify_ids;
// Deleting the registration should move the resources to the purgeable list
// but keep them available.
storage()->DeleteRegistration(
registration_->id(),
scope_.GetOrigin(),
base::Bind(&VerifyPurgeableListStatusCallback,
base::Unretained(storage()->database_.get()),
&verify_ids,
&was_called,
&result));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_OK, result);
EXPECT_EQ(2u, verify_ids.size());
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_EQ(2u, verify_ids.size());
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, true));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, true));
// Also add an uncommitted resource.
int64_t kStaleUncommittedResourceId = storage()->NewResourceId();
storage()->StoreUncommittedResourceId(kStaleUncommittedResourceId);
base::RunLoop().RunUntilIdle();
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetUncommittedResourceIds(&verify_ids));
EXPECT_EQ(1u, verify_ids.size());
WriteBasicResponse(storage(), kStaleUncommittedResourceId);
EXPECT_TRUE(
VerifyBasicResponse(storage(), kStaleUncommittedResourceId, true));
// Simulate browser shutdown. The purgeable and uncommitted resources are now
// stale.
InitializeTestHelper();
LazyInitialize();
// Store a new uncommitted resource. This triggers stale resource cleanup.
int64_t kNewResourceId = storage()->NewResourceId();
WriteBasicResponse(storage(), kNewResourceId);
storage()->StoreUncommittedResourceId(kNewResourceId);
base::RunLoop().RunUntilIdle();
// The stale resources should be purged, but the new resource should persist.
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetUncommittedResourceIds(&verify_ids));
ASSERT_EQ(1u, verify_ids.size());
EXPECT_EQ(kNewResourceId, *verify_ids.begin());
// Purging resources needs interactions with SimpleCache's worker thread,
// so single RunUntilIdle() call may not be sufficient.
while (storage()->is_purge_pending_)
base::RunLoop().RunUntilIdle();
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_TRUE(verify_ids.empty());
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
EXPECT_FALSE(
VerifyBasicResponse(storage(), kStaleUncommittedResourceId, false));
EXPECT_TRUE(VerifyBasicResponse(storage(), kNewResourceId, true));
}
TEST_F(ServiceWorkerResourceStorageDiskTest, DeleteAndStartOver) {
EXPECT_FALSE(storage()->IsDisabled());
ASSERT_TRUE(base::DirectoryExists(storage()->GetDiskCachePath()));
ASSERT_TRUE(base::DirectoryExists(storage()->GetDatabasePath()));
base::RunLoop run_loop;
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->DeleteAndStartOver(
base::Bind(&StatusAndQuitCallback, &status, run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_TRUE(storage()->IsDisabled());
EXPECT_FALSE(base::DirectoryExists(storage()->GetDiskCachePath()));
EXPECT_FALSE(base::DirectoryExists(storage()->GetDatabasePath()));
}
TEST_F(ServiceWorkerResourceStorageDiskTest,
DeleteAndStartOver_UnrelatedFileExists) {
EXPECT_FALSE(storage()->IsDisabled());
ASSERT_TRUE(base::DirectoryExists(storage()->GetDiskCachePath()));
ASSERT_TRUE(base::DirectoryExists(storage()->GetDatabasePath()));
// Create an unrelated file in the database directory to make sure such a file
// does not prevent DeleteAndStartOver.
base::FilePath file_path;
ASSERT_TRUE(
base::CreateTemporaryFileInDir(storage()->GetDatabasePath(), &file_path));
ASSERT_TRUE(base::PathExists(file_path));
base::RunLoop run_loop;
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->DeleteAndStartOver(
base::Bind(&StatusAndQuitCallback, &status, run_loop.QuitClosure()));
run_loop.Run();
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_TRUE(storage()->IsDisabled());
EXPECT_FALSE(base::DirectoryExists(storage()->GetDiskCachePath()));
EXPECT_FALSE(base::DirectoryExists(storage()->GetDatabasePath()));
}
TEST_F(ServiceWorkerResourceStorageDiskTest,
DeleteAndStartOver_OpenedFileExists) {
EXPECT_FALSE(storage()->IsDisabled());
ASSERT_TRUE(base::DirectoryExists(storage()->GetDiskCachePath()));
ASSERT_TRUE(base::DirectoryExists(storage()->GetDatabasePath()));
// Create an unrelated opened file in the database directory to make sure such
// a file does not prevent DeleteAndStartOver on non-Windows platforms.
base::FilePath file_path;
base::ScopedFILE file(base::CreateAndOpenTemporaryFileInDir(
storage()->GetDatabasePath(), &file_path));
ASSERT_TRUE(file);
ASSERT_TRUE(base::PathExists(file_path));
base::RunLoop run_loop;
ServiceWorkerStatusCode status = SERVICE_WORKER_ERROR_MAX_VALUE;
storage()->DeleteAndStartOver(
base::Bind(&StatusAndQuitCallback, &status, run_loop.QuitClosure()));
run_loop.Run();
#if defined(OS_WIN)
// On Windows, deleting the directory containing an opened file should fail.
EXPECT_EQ(SERVICE_WORKER_ERROR_FAILED, status);
EXPECT_TRUE(storage()->IsDisabled());
EXPECT_TRUE(base::DirectoryExists(storage()->GetDiskCachePath()));
EXPECT_TRUE(base::DirectoryExists(storage()->GetDatabasePath()));
#else
EXPECT_EQ(SERVICE_WORKER_OK, status);
EXPECT_TRUE(storage()->IsDisabled());
EXPECT_FALSE(base::DirectoryExists(storage()->GetDiskCachePath()));
EXPECT_FALSE(base::DirectoryExists(storage()->GetDatabasePath()));
#endif
}
TEST_F(ServiceWorkerResourceStorageTest, UpdateRegistration) {
// Promote the worker to active worker and add a controllee.
registration_->SetActiveVersion(registration_->waiting_version());
storage()->UpdateToActiveState(
registration_.get(), base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
ServiceWorkerRemoteProviderEndpoint remote_endpoint;
std::unique_ptr<ServiceWorkerProviderHost> host = CreateProviderHostForWindow(
33 /* dummy render process id */, 1 /* dummy provider_id */,
true /* is_parent_frame_secure */, helper_->context()->AsWeakPtr(),
&remote_endpoint);
registration_->active_version()->AddControllee(host.get());
bool was_called = false;
ServiceWorkerStatusCode result = SERVICE_WORKER_ERROR_FAILED;
std::set<int64_t> verify_ids;
// Make an updated registration.
scoped_refptr<ServiceWorkerVersion> live_version = new ServiceWorkerVersion(
registration_.get(), script_, storage()->NewVersionId(),
context()->AsWeakPtr());
live_version->SetStatus(ServiceWorkerVersion::NEW);
registration_->SetWaitingVersion(live_version);
std::vector<ServiceWorkerDatabase::ResourceRecord> records;
records.push_back(ServiceWorkerDatabase::ResourceRecord(
10, live_version->script_url(), 100));
live_version->script_cache_map()->SetResources(records);
live_version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
// Writing the registration should move the old version's resources to the
// purgeable list but keep them available.
storage()->StoreRegistration(
registration_.get(),
registration_->waiting_version(),
base::Bind(&VerifyPurgeableListStatusCallback,
base::Unretained(storage()->database_.get()),
&verify_ids,
&was_called,
&result));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(was_called);
EXPECT_EQ(SERVICE_WORKER_OK, result);
EXPECT_EQ(2u, verify_ids.size());
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_EQ(2u, verify_ids.size());
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id1_, false));
EXPECT_TRUE(VerifyBasicResponse(storage(), resource_id2_, false));
// Removing the controllee should cause the old version's resources to be
// deleted.
registration_->active_version()->RemoveControllee(host.get());
registration_->active_version()->Doom();
base::RunLoop().RunUntilIdle();
verify_ids.clear();
EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
storage()->database_->GetPurgeableResourceIds(&verify_ids));
EXPECT_TRUE(verify_ids.empty());
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id1_, false));
EXPECT_FALSE(VerifyBasicResponse(storage(), resource_id2_, false));
}
TEST_F(ServiceWorkerStorageTest, FindRegistration_LongestScopeMatch) {
const GURL kDocumentUrl("http://www.example.com/scope/foo");
scoped_refptr<ServiceWorkerRegistration> found_registration;
// Registration for "/scope/".
const GURL kScope1("http://www.example.com/scope/");
const GURL kScript1("http://www.example.com/script1.js");
const int64_t kRegistrationId1 = 1;
const int64_t kVersionId1 = 1;
scoped_refptr<ServiceWorkerRegistration> live_registration1 =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope1),
kRegistrationId1, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version1 = new ServiceWorkerVersion(
live_registration1.get(), kScript1, kVersionId1, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records1;
records1.push_back(ServiceWorkerDatabase::ResourceRecord(
1, live_version1->script_url(), 100));
live_version1->script_cache_map()->SetResources(records1);
live_version1->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
live_version1->SetStatus(ServiceWorkerVersion::INSTALLED);
live_registration1->SetWaitingVersion(live_version1);
// Registration for "/scope/foo".
const GURL kScope2("http://www.example.com/scope/foo");
const GURL kScript2("http://www.example.com/script2.js");
const int64_t kRegistrationId2 = 2;
const int64_t kVersionId2 = 2;
scoped_refptr<ServiceWorkerRegistration> live_registration2 =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope2),
kRegistrationId2, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version2 = new ServiceWorkerVersion(
live_registration2.get(), kScript2, kVersionId2, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records2;
records2.push_back(ServiceWorkerDatabase::ResourceRecord(
2, live_version2->script_url(), 100));
live_version2->script_cache_map()->SetResources(records2);
live_version2->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
live_version2->SetStatus(ServiceWorkerVersion::INSTALLED);
live_registration2->SetWaitingVersion(live_version2);
// Registration for "/scope/foobar".
const GURL kScope3("http://www.example.com/scope/foobar");
const GURL kScript3("http://www.example.com/script3.js");
const int64_t kRegistrationId3 = 3;
const int64_t kVersionId3 = 3;
scoped_refptr<ServiceWorkerRegistration> live_registration3 =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope3),
kRegistrationId3, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version3 = new ServiceWorkerVersion(
live_registration3.get(), kScript3, kVersionId3, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records3;
records3.push_back(ServiceWorkerDatabase::ResourceRecord(
3, live_version3->script_url(), 100));
live_version3->script_cache_map()->SetResources(records3);
live_version3->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
live_version3->SetStatus(ServiceWorkerVersion::INSTALLED);
live_registration3->SetWaitingVersion(live_version3);
// Notify storage of they being installed.
storage()->NotifyInstallingRegistration(live_registration1.get());
storage()->NotifyInstallingRegistration(live_registration2.get());
storage()->NotifyInstallingRegistration(live_registration3.get());
// Find a registration among installing ones.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
EXPECT_EQ(live_registration2, found_registration);
found_registration = NULL;
// Store registrations.
EXPECT_EQ(SERVICE_WORKER_OK,
StoreRegistration(live_registration1, live_version1));
EXPECT_EQ(SERVICE_WORKER_OK,
StoreRegistration(live_registration2, live_version2));
EXPECT_EQ(SERVICE_WORKER_OK,
StoreRegistration(live_registration3, live_version3));
// Notify storage of installations no longer happening.
storage()->NotifyDoneInstallingRegistration(
live_registration1.get(), NULL, SERVICE_WORKER_OK);
storage()->NotifyDoneInstallingRegistration(
live_registration2.get(), NULL, SERVICE_WORKER_OK);
storage()->NotifyDoneInstallingRegistration(
live_registration3.get(), NULL, SERVICE_WORKER_OK);
// Find a registration among installed ones.
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kDocumentUrl, &found_registration));
EXPECT_EQ(live_registration2, found_registration);
}
// Test fixture that uses disk storage, rather than memory. Useful for tests
// that test persistence by simulating browser shutdown and restart.
class ServiceWorkerStorageDiskTest : public ServiceWorkerStorageTest {
public:
void SetUp() override {
ASSERT_TRUE(InitUserDataDirectory());
ServiceWorkerStorageTest::SetUp();
}
};
TEST_F(ServiceWorkerStorageDiskTest, OriginHasForeignFetchRegistrations) {
LazyInitialize();
// Registration 1 for http://www.example.com
const GURL kScope1("http://www.example.com/scope/");
const GURL kScript1("http://www.example.com/script1.js");
const int64_t kRegistrationId1 = 1;
const int64_t kVersionId1 = 1;
scoped_refptr<ServiceWorkerRegistration> live_registration1 =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope1),
kRegistrationId1, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version1 = new ServiceWorkerVersion(
live_registration1.get(), kScript1, kVersionId1, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records1;
records1.push_back(ServiceWorkerDatabase::ResourceRecord(
1, live_version1->script_url(), 100));
live_version1->script_cache_map()->SetResources(records1);
live_version1->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
live_version1->SetStatus(ServiceWorkerVersion::INSTALLED);
live_version1->set_foreign_fetch_scopes(std::vector<GURL>(1, kScope1));
live_registration1->SetWaitingVersion(live_version1);
// Registration 2 for http://www.example.com
const GURL kScope2("http://www.example.com/scope/foo");
const GURL kScript2("http://www.example.com/script2.js");
const int64_t kRegistrationId2 = 2;
const int64_t kVersionId2 = 2;
scoped_refptr<ServiceWorkerRegistration> live_registration2 =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope2),
kRegistrationId2, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version2 = new ServiceWorkerVersion(
live_registration2.get(), kScript2, kVersionId2, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records2;
records2.push_back(ServiceWorkerDatabase::ResourceRecord(
2, live_version2->script_url(), 100));
live_version2->script_cache_map()->SetResources(records2);
live_version2->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
live_version2->SetStatus(ServiceWorkerVersion::INSTALLED);
live_version2->set_foreign_fetch_scopes(std::vector<GURL>(1, kScope2));
live_registration2->SetWaitingVersion(live_version2);
// Registration for http://www.test.com
const GURL kScope3("http://www.test.com/scope/foobar");
const GURL kScript3("http://www.test.com/script3.js");
const int64_t kRegistrationId3 = 3;
const int64_t kVersionId3 = 3;
scoped_refptr<ServiceWorkerRegistration> live_registration3 =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope3),
kRegistrationId3, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> live_version3 = new ServiceWorkerVersion(
live_registration3.get(), kScript3, kVersionId3, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> records3;
records3.push_back(ServiceWorkerDatabase::ResourceRecord(
3, live_version3->script_url(), 100));
live_version3->script_cache_map()->SetResources(records3);
live_version3->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
live_version3->SetStatus(ServiceWorkerVersion::INSTALLED);
live_registration3->SetWaitingVersion(live_version3);
// Neither origin should have registrations before they are stored.
const GURL kOrigin1 = kScope1.GetOrigin();
const GURL kOrigin2 = kScope3.GetOrigin();
EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));
// Store all registrations.
EXPECT_EQ(SERVICE_WORKER_OK,
StoreRegistration(live_registration1, live_version1));
EXPECT_EQ(SERVICE_WORKER_OK,
StoreRegistration(live_registration2, live_version2));
EXPECT_EQ(SERVICE_WORKER_OK,
StoreRegistration(live_registration3, live_version3));
// Now first origin should have foreign fetch registrations, second doesn't.
EXPECT_TRUE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));
// Remove one registration at first origin.
EXPECT_EQ(SERVICE_WORKER_OK,
DeleteRegistration(kRegistrationId1, kScope1.GetOrigin()));
// First origin should still have a registration left.
EXPECT_TRUE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));
// Simulate browser shutdown and restart.
live_registration1 = nullptr;
live_version1 = nullptr;
live_registration2 = nullptr;
live_version2 = nullptr;
live_registration3 = nullptr;
live_version3 = nullptr;
InitializeTestHelper();
LazyInitialize();
// First origin should still have a registration left.
EXPECT_TRUE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));
// Remove other registration at first origin.
EXPECT_EQ(SERVICE_WORKER_OK,
DeleteRegistration(kRegistrationId2, kScope2.GetOrigin()));
// No foreign fetch registrations remain.
EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin1));
EXPECT_FALSE(storage()->OriginHasForeignFetchRegistrations(kOrigin2));
}
TEST_F(ServiceWorkerStorageTest, OriginTrialsAbsentEntryAndEmptyEntry) {
const GURL origin1("http://www1.example.com");
const GURL scope1("http://www1.example.com/foo/");
RegistrationData data1;
data1.registration_id = 100;
data1.scope = scope1;
data1.script = GURL(origin1.spec() + "/script.js");
data1.version_id = 1000;
data1.is_active = true;
data1.resources_total_size_bytes = 100;
// Don't set origin_trial_tokens to simulate old database entry.
std::vector<ServiceWorkerDatabase::ResourceRecord> resources1;
resources1.push_back(
ServiceWorkerDatabase::ResourceRecord(1, data1.script, 100));
WriteRegistration(data1, resources1);
const GURL origin2("http://www2.example.com");
const GURL scope2("http://www2.example.com/foo/");
RegistrationData data2;
data2.registration_id = 200;
data2.scope = scope2;
data2.script = GURL(origin2.spec() + "/script.js");
data2.version_id = 2000;
data2.is_active = true;
data2.resources_total_size_bytes = 200;
// Set empty origin_trial_tokens.
data2.origin_trial_tokens = TrialTokenValidator::FeatureToTokensMap();
std::vector<ServiceWorkerDatabase::ResourceRecord> resources2;
resources2.push_back(
ServiceWorkerDatabase::ResourceRecord(2, data2.script, 200));
WriteRegistration(data2, resources2);
scoped_refptr<ServiceWorkerRegistration> found_registration;
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(scope1, &found_registration));
ASSERT_TRUE(found_registration->active_version());
// origin_trial_tokens must be unset.
EXPECT_FALSE(found_registration->active_version()->origin_trial_tokens());
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(scope2, &found_registration));
ASSERT_TRUE(found_registration->active_version());
// Empty origin_trial_tokens must exist.
ASSERT_TRUE(found_registration->active_version()->origin_trial_tokens());
EXPECT_TRUE(
found_registration->active_version()->origin_trial_tokens()->empty());
}
class ServiceWorkerStorageOriginTrialsDiskTest
: public ServiceWorkerStorageTest {
public:
ServiceWorkerStorageOriginTrialsDiskTest() {
SetContentClient(&test_content_client_);
}
~ServiceWorkerStorageOriginTrialsDiskTest() override {
SetContentClient(nullptr);
}
void SetUp() override {
ASSERT_TRUE(InitUserDataDirectory());
ServiceWorkerStorageTest::SetUp();
}
private:
class TestOriginTrialPolicy : public OriginTrialPolicy {
public:
base::StringPiece GetPublicKey() const override {
return base::StringPiece(reinterpret_cast<const char*>(kTestPublicKey),
arraysize(kTestPublicKey));
}
};
class TestContentClient : public ContentClient {
public:
// ContentRendererClient methods
OriginTrialPolicy* GetOriginTrialPolicy() override {
return &origin_trial_policy_;
}
private:
TestOriginTrialPolicy origin_trial_policy_;
};
TestContentClient test_content_client_;
};
TEST_F(ServiceWorkerStorageOriginTrialsDiskTest, FromMainScript) {
LazyInitialize();
const GURL kScope("https://valid.example.com/scope");
const GURL kScript("https://valid.example.com/script.js");
const int64_t kRegistrationId = 1;
const int64_t kVersionId = 1;
scoped_refptr<ServiceWorkerRegistration> registration =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
registration.get(), kScript, kVersionId, context()->AsWeakPtr());
net::HttpResponseInfo http_info;
http_info.ssl_info.cert =
net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
EXPECT_TRUE(http_info.ssl_info.is_valid());
http_info.ssl_info.security_bits = 0x100;
// SSL3 TLS_DHE_RSA_WITH_AES_256_CBC_SHA
http_info.ssl_info.connection_status = 0x300039;
const std::string kHTTPHeaderLine("HTTP/1.1 200 OK\n\n");
const std::string kOriginTrial("Origin-Trial: ");
// Token for Feature1 which expires 2033-05-18.
// generate_token.py valid.example.com Feature1 --expire-timestamp=2000000000
// TODO(horo): Generate this sample token during the build.
const std::string kFeature1Token(
"AtiUXksymWhTv5ipBE7853JytiYb0RMj3wtEBjqu3PeufQPwV1oEaNjHt4R/oEBfcK0UiWlA"
"P2b9BE2/eThqcAYAAABYeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
"NDMiLCAiZmVhdHVyZSI6ICJGZWF0dXJlMSIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==");
// Token for Feature2 which expires 2033-05-18.
// generate_token.py valid.example.com Feature2 --expire-timestamp=2000000000
// TODO(horo): Generate this sample token during the build.
const std::string kFeature2Token1(
"ApmHVC6Dpez0KQNBy13o6cGuoB5AgzOLN0keQMyAN5mjebCwR0MA8/IyjKQIlyom2RuJVg/u"
"LmnqEpldfewkbA8AAABYeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
"NDMiLCAiZmVhdHVyZSI6ICJGZWF0dXJlMiIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==");
// Token for Feature2 which expires 2036-07-18.
// generate_token.py valid.example.com Feature2 --expire-timestamp=2100000000
// TODO(horo): Generate this sample token during the build.
const std::string kFeature2Token2(
"AmV2SSxrYstE2zSwZToy7brAbIJakd146apC/6+VDflLmc5yDfJlHGILe5+ZynlcliG7clOR"
"fHhXCzS5Lh1v4AAAAABYeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
"NDMiLCAiZmVhdHVyZSI6ICJGZWF0dXJlMiIsICJleHBpcnkiOiAyMTAwMDAwMDAwfQ==");
// Token for Feature3 which expired 2001-09-09.
// generate_token.py valid.example.com Feature3 --expire-timestamp=1000000000
const std::string kFeature3ExpiredToken(
"AtSAc03z4qvid34W4MHMxyRFUJKlubZ+P5cs5yg6EiBWcagVbnm5uBgJMJN34pag7D5RywGV"
"ol2RFf+4Sdm1hQ4AAABYeyJvcmlnaW4iOiAiaHR0cHM6Ly92YWxpZC5leGFtcGxlLmNvbTo0"
"NDMiLCAiZmVhdHVyZSI6ICJGZWF0dXJlMyIsICJleHBpcnkiOiAxMDAwMDAwMDAwfQ==");
http_info.headers = make_scoped_refptr(new net::HttpResponseHeaders(""));
http_info.headers->AddHeader(kOriginTrial + kFeature1Token);
http_info.headers->AddHeader(kOriginTrial + kFeature2Token1);
http_info.headers->AddHeader(kOriginTrial + kFeature2Token2);
http_info.headers->AddHeader(kOriginTrial + kFeature3ExpiredToken);
version->SetMainScriptHttpResponseInfo(http_info);
ASSERT_TRUE(version->origin_trial_tokens());
const TrialTokenValidator::FeatureToTokensMap& tokens =
*version->origin_trial_tokens();
ASSERT_EQ(2UL, tokens.size());
ASSERT_EQ(1UL, tokens.at("Feature1").size());
EXPECT_EQ(kFeature1Token, tokens.at("Feature1")[0]);
ASSERT_EQ(2UL, tokens.at("Feature2").size());
EXPECT_EQ(kFeature2Token1, tokens.at("Feature2")[0]);
EXPECT_EQ(kFeature2Token2, tokens.at("Feature2")[1]);
std::vector<ServiceWorkerDatabase::ResourceRecord> record;
record.push_back(ServiceWorkerDatabase::ResourceRecord(1, kScript, 100));
version->script_cache_map()->SetResources(record);
version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version->SetStatus(ServiceWorkerVersion::INSTALLED);
registration->SetActiveVersion(version);
EXPECT_EQ(SERVICE_WORKER_OK, StoreRegistration(registration, version));
// Simulate browser shutdown and restart.
registration = nullptr;
version = nullptr;
InitializeTestHelper();
LazyInitialize();
scoped_refptr<ServiceWorkerRegistration> found_registration;
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kScope, &found_registration));
ASSERT_TRUE(found_registration->active_version());
const TrialTokenValidator::FeatureToTokensMap& found_tokens =
*found_registration->active_version()->origin_trial_tokens();
ASSERT_EQ(2UL, found_tokens.size());
ASSERT_EQ(1UL, found_tokens.at("Feature1").size());
EXPECT_EQ(kFeature1Token, found_tokens.at("Feature1")[0]);
ASSERT_EQ(2UL, found_tokens.at("Feature2").size());
EXPECT_EQ(kFeature2Token1, found_tokens.at("Feature2")[0]);
EXPECT_EQ(kFeature2Token2, found_tokens.at("Feature2")[1]);
}
// Tests loading a registration that has no navigation preload state.
TEST_F(ServiceWorkerStorageTest, AbsentNavigationPreloadState) {
const GURL origin1("http://www1.example.com");
const GURL scope1("http://www1.example.com/foo/");
RegistrationData data1;
data1.registration_id = 100;
data1.scope = scope1;
data1.script = GURL(origin1.spec() + "/script.js");
data1.version_id = 1000;
data1.is_active = true;
data1.resources_total_size_bytes = 100;
// Don't set navigation preload state to simulate old database entry.
std::vector<ServiceWorkerDatabase::ResourceRecord> resources1;
resources1.push_back(
ServiceWorkerDatabase::ResourceRecord(1, data1.script, 100));
WriteRegistration(data1, resources1);
scoped_refptr<ServiceWorkerRegistration> found_registration;
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(scope1, &found_registration));
const NavigationPreloadState& registration_state =
found_registration->navigation_preload_state();
EXPECT_FALSE(registration_state.enabled);
EXPECT_EQ("true", registration_state.header);
ASSERT_TRUE(found_registration->active_version());
const NavigationPreloadState& state =
found_registration->active_version()->navigation_preload_state();
EXPECT_FALSE(state.enabled);
EXPECT_EQ("true", state.header);
}
// Tests loading a registration with a disabled navigation preload
// state.
TEST_F(ServiceWorkerStorageDiskTest, DisabledNavigationPreloadState) {
LazyInitialize();
const GURL kScope("https://valid.example.com/scope");
const GURL kScript("https://valid.example.com/script.js");
const int64_t kRegistrationId = 1;
const int64_t kVersionId = 1;
scoped_refptr<ServiceWorkerRegistration> registration =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
registration.get(), kScript, kVersionId, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> record;
record.push_back(ServiceWorkerDatabase::ResourceRecord(1, kScript, 100));
version->script_cache_map()->SetResources(record);
version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration->SetActiveVersion(version);
registration->EnableNavigationPreload(false);
EXPECT_EQ(SERVICE_WORKER_OK, StoreRegistration(registration, version));
// Simulate browser shutdown and restart.
registration = nullptr;
version = nullptr;
InitializeTestHelper();
LazyInitialize();
scoped_refptr<ServiceWorkerRegistration> found_registration;
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kScope, &found_registration));
const NavigationPreloadState& registration_state =
found_registration->navigation_preload_state();
EXPECT_FALSE(registration_state.enabled);
EXPECT_EQ("true", registration_state.header);
ASSERT_TRUE(found_registration->active_version());
const NavigationPreloadState& state =
found_registration->active_version()->navigation_preload_state();
EXPECT_FALSE(state.enabled);
EXPECT_EQ("true", state.header);
}
// Tests loading a registration with an enabled navigation preload state, as
// well as a custom header value.
TEST_F(ServiceWorkerStorageDiskTest, EnabledNavigationPreloadState) {
LazyInitialize();
const GURL kScope("https://valid.example.com/scope");
const GURL kScript("https://valid.example.com/script.js");
const std::string kHeaderValue("custom header value");
const int64_t kRegistrationId = 1;
const int64_t kVersionId = 1;
scoped_refptr<ServiceWorkerRegistration> registration =
new ServiceWorkerRegistration(ServiceWorkerRegistrationOptions(kScope),
kRegistrationId, context()->AsWeakPtr());
scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion(
registration.get(), kScript, kVersionId, context()->AsWeakPtr());
std::vector<ServiceWorkerDatabase::ResourceRecord> record;
record.push_back(ServiceWorkerDatabase::ResourceRecord(1, kScript, 100));
version->script_cache_map()->SetResources(record);
version->set_fetch_handler_existence(
ServiceWorkerVersion::FetchHandlerExistence::EXISTS);
version->SetStatus(ServiceWorkerVersion::ACTIVATED);
registration->SetActiveVersion(version);
registration->EnableNavigationPreload(true);
registration->SetNavigationPreloadHeader(kHeaderValue);
EXPECT_EQ(SERVICE_WORKER_OK, StoreRegistration(registration, version));
// Simulate browser shutdown and restart.
registration = nullptr;
version = nullptr;
InitializeTestHelper();
LazyInitialize();
scoped_refptr<ServiceWorkerRegistration> found_registration;
EXPECT_EQ(SERVICE_WORKER_OK,
FindRegistrationForDocument(kScope, &found_registration));
const NavigationPreloadState& registration_state =
found_registration->navigation_preload_state();
EXPECT_TRUE(registration_state.enabled);
EXPECT_EQ(kHeaderValue, registration_state.header);
ASSERT_TRUE(found_registration->active_version());
const NavigationPreloadState& state =
found_registration->active_version()->navigation_preload_state();
EXPECT_TRUE(state.enabled);
EXPECT_EQ(kHeaderValue, state.header);
}
} // namespace content