// Copyright 2014 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_database.h" #include "base/files/file_util.h" #include "base/lazy_instance.h" #include "base/location.h" #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "content/browser/service_worker/service_worker_database.pb.h" #include "content/browser/service_worker/service_worker_metrics.h" #include "content/common/service_worker/service_worker_types.h" #include "third_party/leveldatabase/env_chromium.h" #include "third_party/leveldatabase/src/helpers/memenv/memenv.h" #include "third_party/leveldatabase/src/include/leveldb/db.h" #include "third_party/leveldatabase/src/include/leveldb/env.h" #include "third_party/leveldatabase/src/include/leveldb/write_batch.h" // LevelDB database schema // ======================= // // NOTE // - int64 value is serialized as a string by base::Int64ToString(). // - GURL value is serialized as a string by GURL::spec(). // // Version 1 (in sorted order) // key: "INITDATA_DB_VERSION" // value: "1" // // key: "INITDATA_NEXT_REGISTRATION_ID" // value: // // key: "INITDATA_NEXT_RESOURCE_ID" // value: // // key: "INITDATA_NEXT_VERSION_ID" // value: // // key: "INITDATA_UNIQUE_ORIGIN:" + // value: // // key: "PRES:" + // value: // // key: "REG:" + + '\x00' + // (ex. "REG:http://example.com\x00123456") // value: // // key: "REG_HAS_USER_DATA:" + + '\x00' // + // value: // // key: "REG_USER_DATA:" + + '\x00' // + // (ex. "REG_USER_DATA:123456\x00foo_bar") // value: // // key: "RES:" + + '\x00' + // (ex. "RES:123456\x00654321") // value: // // key: "URES:" + // value: // // Version 2 // // key: "REGID_TO_ORIGIN:" + // value: // // key: "INITDATA_DISKCACHE_MIGRATION_NOT_NEEDED" // value: // - This entry represents that the diskcache uses the Simple backend and // does not have to do diskcache migration (http://crbug.com/487482). // // key: "INITDATA_OLD_DISKCACHE_DELETION_NOT_NEEDED" // value: // - This entry represents that the old BlockFile diskcache was deleted // after diskcache migration (http://crbug.com/487482). namespace content { namespace { const char kDatabaseVersionKey[] = "INITDATA_DB_VERSION"; const char kNextRegIdKey[] = "INITDATA_NEXT_REGISTRATION_ID"; const char kNextResIdKey[] = "INITDATA_NEXT_RESOURCE_ID"; const char kNextVerIdKey[] = "INITDATA_NEXT_VERSION_ID"; const char kUniqueOriginKey[] = "INITDATA_UNIQUE_ORIGIN:"; const char kDiskCacheMigrationNotNeededKey[] = "INITDATA_DISKCACHE_MIGRATION_NOT_NEEDED"; const char kOldDiskCacheDeletionNotNeededKey[] = "INITDATA_OLD_DISKCACHE_DELETION_NOT_NEEDED"; const char kRegKeyPrefix[] = "REG:"; const char kRegUserDataKeyPrefix[] = "REG_USER_DATA:"; const char kRegHasUserDataKeyPrefix[] = "REG_HAS_USER_DATA:"; const char kRegIdToOriginKeyPrefix[] = "REGID_TO_ORIGIN:"; const char kResKeyPrefix[] = "RES:"; const char kKeySeparator = '\x00'; const char kEmptyValue[] = ""; const char kUncommittedResIdKeyPrefix[] = "URES:"; const char kPurgeableResIdKeyPrefix[] = "PRES:"; const int64 kCurrentSchemaVersion = 2; class ServiceWorkerEnv : public leveldb_env::ChromiumEnv { public: ServiceWorkerEnv() : ChromiumEnv("LevelDBEnv.ServiceWorker", false /* make_backup */) {} }; base::LazyInstance::Leaky g_service_worker_env = LAZY_INSTANCE_INITIALIZER; bool RemovePrefix(const std::string& str, const std::string& prefix, std::string* out) { if (!base::StartsWith(str, prefix, base::CompareCase::SENSITIVE)) return false; if (out) *out = str.substr(prefix.size()); return true; } std::string CreateRegistrationKeyPrefix(const GURL& origin) { return base::StringPrintf("%s%s%c", kRegKeyPrefix, origin.GetOrigin().spec().c_str(), kKeySeparator); } std::string CreateRegistrationKey(int64 registration_id, const GURL& origin) { return CreateRegistrationKeyPrefix(origin) .append(base::Int64ToString(registration_id)); } std::string CreateResourceRecordKeyPrefix(int64 version_id) { return base::StringPrintf("%s%s%c", kResKeyPrefix, base::Int64ToString(version_id).c_str(), kKeySeparator); } std::string CreateResourceRecordKey(int64 version_id, int64 resource_id) { return CreateResourceRecordKeyPrefix(version_id).append( base::Int64ToString(resource_id)); } std::string CreateUniqueOriginKey(const GURL& origin) { return base::StringPrintf("%s%s", kUniqueOriginKey, origin.GetOrigin().spec().c_str()); } std::string CreateResourceIdKey(const char* key_prefix, int64 resource_id) { return base::StringPrintf( "%s%s", key_prefix, base::Int64ToString(resource_id).c_str()); } std::string CreateUserDataKeyPrefix(int64 registration_id) { return base::StringPrintf("%s%s%c", kRegUserDataKeyPrefix, base::Int64ToString(registration_id).c_str(), kKeySeparator); } std::string CreateUserDataKey(int64 registration_id, const std::string& user_data_name) { return CreateUserDataKeyPrefix(registration_id).append(user_data_name); } std::string CreateHasUserDataKeyPrefix(const std::string& user_data_name) { return base::StringPrintf("%s%s%c", kRegHasUserDataKeyPrefix, user_data_name.c_str(), kKeySeparator); } std::string CreateHasUserDataKey(int64 registration_id, const std::string& user_data_name) { return CreateHasUserDataKeyPrefix(user_data_name) .append(base::Int64ToString(registration_id)); } std::string CreateRegistrationIdToOriginKey(int64 registration_id) { return base::StringPrintf("%s%s", kRegIdToOriginKeyPrefix, base::Int64ToString(registration_id).c_str()); } void PutRegistrationDataToBatch( const ServiceWorkerDatabase::RegistrationData& input, leveldb::WriteBatch* batch) { DCHECK(batch); // Convert RegistrationData to ServiceWorkerRegistrationData. ServiceWorkerRegistrationData data; data.set_registration_id(input.registration_id); data.set_scope_url(input.scope.spec()); data.set_script_url(input.script.spec()); data.set_version_id(input.version_id); data.set_is_active(input.is_active); data.set_has_fetch_handler(input.has_fetch_handler); data.set_last_update_check_time(input.last_update_check.ToInternalValue()); data.set_resources_total_size_bytes(input.resources_total_size_bytes); std::string value; bool success = data.SerializeToString(&value); DCHECK(success); GURL origin = input.scope.GetOrigin(); batch->Put(CreateRegistrationKey(data.registration_id(), origin), value); } void PutResourceRecordToBatch( const ServiceWorkerDatabase::ResourceRecord& input, int64 version_id, leveldb::WriteBatch* batch) { DCHECK(batch); DCHECK_GE(input.size_bytes, 0); // Convert ResourceRecord to ServiceWorkerResourceRecord. ServiceWorkerResourceRecord record; record.set_resource_id(input.resource_id); record.set_url(input.url.spec()); record.set_size_bytes(input.size_bytes); std::string value; bool success = record.SerializeToString(&value); DCHECK(success); batch->Put(CreateResourceRecordKey(version_id, input.resource_id), value); } void PutUniqueOriginToBatch(const GURL& origin, leveldb::WriteBatch* batch) { // Value should be empty. batch->Put(CreateUniqueOriginKey(origin), ""); } void PutPurgeableResourceIdToBatch(int64 resource_id, leveldb::WriteBatch* batch) { // Value should be empty. batch->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix, resource_id), ""); } ServiceWorkerDatabase::Status ParseId( const std::string& serialized, int64* out) { DCHECK(out); int64 id; if (!base::StringToInt64(serialized, &id) || id < 0) return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; *out = id; return ServiceWorkerDatabase::STATUS_OK; } ServiceWorkerDatabase::Status ParseDatabaseVersion( const std::string& serialized, int64* out) { DCHECK(out); const int kFirstValidVersion = 1; int64 version; if (!base::StringToInt64(serialized, &version) || version < kFirstValidVersion) { return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; } if (kCurrentSchemaVersion < version) { DLOG(ERROR) << "ServiceWorkerDatabase has newer schema version" << " than the current latest version: " << version << " vs " << kCurrentSchemaVersion; return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; } *out = version; return ServiceWorkerDatabase::STATUS_OK; } ServiceWorkerDatabase::Status ParseRegistrationData( const std::string& serialized, ServiceWorkerDatabase::RegistrationData* out) { DCHECK(out); ServiceWorkerRegistrationData data; if (!data.ParseFromString(serialized)) return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; GURL scope_url(data.scope_url()); GURL script_url(data.script_url()); if (!scope_url.is_valid() || !script_url.is_valid() || scope_url.GetOrigin() != script_url.GetOrigin()) { DLOG(ERROR) << "Scope URL '" << data.scope_url() << "' and/or script url '" << data.script_url() << "' are invalid or have mismatching origins."; return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; } // Convert ServiceWorkerRegistrationData to RegistrationData. out->registration_id = data.registration_id(); out->scope = scope_url; out->script = script_url; out->version_id = data.version_id(); out->is_active = data.is_active(); out->has_fetch_handler = data.has_fetch_handler(); out->last_update_check = base::Time::FromInternalValue(data.last_update_check_time()); out->resources_total_size_bytes = data.resources_total_size_bytes(); return ServiceWorkerDatabase::STATUS_OK; } ServiceWorkerDatabase::Status ParseResourceRecord( const std::string& serialized, ServiceWorkerDatabase::ResourceRecord* out) { DCHECK(out); ServiceWorkerResourceRecord record; if (!record.ParseFromString(serialized)) return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; GURL url(record.url()); if (!url.is_valid()) return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; // Convert ServiceWorkerResourceRecord to ResourceRecord. out->resource_id = record.resource_id(); out->url = url; out->size_bytes = record.size_bytes(); return ServiceWorkerDatabase::STATUS_OK; } ServiceWorkerDatabase::Status LevelDBStatusToStatus( const leveldb::Status& status) { if (status.ok()) return ServiceWorkerDatabase::STATUS_OK; else if (status.IsNotFound()) return ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND; else if (status.IsIOError()) return ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR; else if (status.IsCorruption()) return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; else return ServiceWorkerDatabase::STATUS_ERROR_FAILED; } int64_t AccumulateResourceSizeInBytes( const std::vector& resources) { int64_t total_size_bytes = 0; for (const auto& resource : resources) total_size_bytes += resource.size_bytes; return total_size_bytes; } } // namespace const char* ServiceWorkerDatabase::StatusToString( ServiceWorkerDatabase::Status status) { switch (status) { case ServiceWorkerDatabase::STATUS_OK: return "Database OK"; case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND: return "Database not found"; case ServiceWorkerDatabase::STATUS_ERROR_IO_ERROR: return "Database IO error"; case ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED: return "Database corrupted"; case ServiceWorkerDatabase::STATUS_ERROR_FAILED: return "Database operation failed"; case ServiceWorkerDatabase::STATUS_ERROR_MAX: NOTREACHED(); return "Database unknown error"; } NOTREACHED(); return "Database unknown error"; } ServiceWorkerDatabase::RegistrationData::RegistrationData() : registration_id(kInvalidServiceWorkerRegistrationId), version_id(kInvalidServiceWorkerVersionId), is_active(false), has_fetch_handler(false), resources_total_size_bytes(0) { } ServiceWorkerDatabase::RegistrationData::~RegistrationData() { } ServiceWorkerDatabase::ServiceWorkerDatabase(const base::FilePath& path) : path_(path), next_avail_registration_id_(0), next_avail_resource_id_(0), next_avail_version_id_(0), state_(UNINITIALIZED), skip_writing_diskcache_migration_state_on_init_for_testing_(false) { sequence_checker_.DetachFromSequence(); } ServiceWorkerDatabase::~ServiceWorkerDatabase() { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); db_.reset(); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetNextAvailableIds( int64* next_avail_registration_id, int64* next_avail_version_id, int64* next_avail_resource_id) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(next_avail_registration_id); DCHECK(next_avail_version_id); DCHECK(next_avail_resource_id); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) { *next_avail_registration_id = 0; *next_avail_version_id = 0; *next_avail_resource_id = 0; return STATUS_OK; } if (status != STATUS_OK) return status; status = ReadNextAvailableId(kNextRegIdKey, &next_avail_registration_id_); if (status != STATUS_OK) return status; status = ReadNextAvailableId(kNextVerIdKey, &next_avail_version_id_); if (status != STATUS_OK) return status; status = ReadNextAvailableId(kNextResIdKey, &next_avail_resource_id_); if (status != STATUS_OK) return status; *next_avail_registration_id = next_avail_registration_id_; *next_avail_version_id = next_avail_version_id_; *next_avail_resource_id = next_avail_resource_id_; return STATUS_OK; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::IsDiskCacheMigrationNeeded( bool* migration_needed) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) { *migration_needed = false; return STATUS_OK; } if (status != STATUS_OK) return status; std::string value; status = LevelDBStatusToStatus(db_->Get( leveldb::ReadOptions(), kDiskCacheMigrationNotNeededKey, &value)); if (status == STATUS_ERROR_NOT_FOUND) { *migration_needed = true; HandleReadResult(FROM_HERE, STATUS_OK); return STATUS_OK; } if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } *migration_needed = false; HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::SetDiskCacheMigrationNotNeeded() { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); Status status = LazyOpen(true); if (status != STATUS_OK) return status; leveldb::WriteBatch batch; batch.Put(kDiskCacheMigrationNotNeededKey, kEmptyValue); return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::IsOldDiskCacheDeletionNeeded(bool* deletion_needed) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) { *deletion_needed = false; return STATUS_OK; } if (status != STATUS_OK) return status; std::string value; status = LevelDBStatusToStatus(db_->Get( leveldb::ReadOptions(), kOldDiskCacheDeletionNotNeededKey, &value)); if (status == STATUS_ERROR_NOT_FOUND) { *deletion_needed = true; HandleReadResult(FROM_HERE, STATUS_OK); return STATUS_OK; } if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } *deletion_needed = false; HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::SetOldDiskCacheDeletionNotNeeded() { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); Status status = LazyOpen(true); if (status != STATUS_OK) return status; leveldb::WriteBatch batch; batch.Put(kOldDiskCacheDeletionNotNeededKey, kEmptyValue); return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set* origins) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(origins->empty()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) { status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); origins->clear(); return status; } std::string origin_str; if (!RemovePrefix(itr->key().ToString(), kUniqueOriginKey, &origin_str)) break; GURL origin(origin_str); if (!origin.is_valid()) { status = STATUS_ERROR_CORRUPTED; HandleReadResult(FROM_HERE, status); origins->clear(); return status; } origins->insert(origin); } HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin( const GURL& origin, std::vector* registrations, std::vector>* opt_resources_list) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(registrations->empty()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; std::string prefix = CreateRegistrationKeyPrefix(origin); scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(prefix); itr->Valid(); itr->Next()) { status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); registrations->clear(); if (opt_resources_list) opt_resources_list->clear(); return status; } if (!RemovePrefix(itr->key().ToString(), prefix, NULL)) break; RegistrationData registration; status = ParseRegistrationData(itr->value().ToString(), ®istration); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); registrations->clear(); if (opt_resources_list) opt_resources_list->clear(); return status; } registrations->push_back(registration); if (opt_resources_list) { std::vector resources; status = ReadResourceRecords(registration.version_id, &resources); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); registrations->clear(); opt_resources_list->clear(); return status; } opt_resources_list->push_back(resources); } } HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations( std::vector* registrations) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(registrations->empty()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) { status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); registrations->clear(); return status; } if (!RemovePrefix(itr->key().ToString(), kRegKeyPrefix, NULL)) break; RegistrationData registration; status = ParseRegistrationData(itr->value().ToString(), ®istration); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); registrations->clear(); return status; } registrations->push_back(registration); } HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistration( int64 registration_id, const GURL& origin, RegistrationData* registration, std::vector* resources) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(registration); DCHECK(resources); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_ERROR_NOT_FOUND; if (status != STATUS_OK) return status; RegistrationData value; status = ReadRegistrationData(registration_id, origin, &value); if (status != STATUS_OK) return status; status = ReadResourceRecords(value.version_id, resources); if (status != STATUS_OK) return status; // ResourceRecord must contain the ServiceWorker's main script. if (resources->empty()) return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; *registration = value; return STATUS_OK; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationOrigin( int64 registration_id, GURL* origin) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(origin); Status status = LazyOpen(true); if (IsNewOrNonexistentDatabase(status)) return STATUS_ERROR_NOT_FOUND; if (status != STATUS_OK) return status; std::string value; status = LevelDBStatusToStatus( db_->Get(leveldb::ReadOptions(), CreateRegistrationIdToOriginKey(registration_id), &value)); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status == STATUS_ERROR_NOT_FOUND ? STATUS_OK : status); return status; } GURL parsed(value); if (!parsed.is_valid()) { status = STATUS_ERROR_CORRUPTED; HandleReadResult(FROM_HERE, status); return status; } *origin = parsed; HandleReadResult(FROM_HERE, STATUS_OK); return STATUS_OK; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteRegistration( const RegistrationData& registration, const std::vector& resources, RegistrationData* old_registration, std::vector* newly_purgeable_resources) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(old_registration); DCHECK(!resources.empty()); Status status = LazyOpen(true); old_registration->version_id = kInvalidServiceWorkerVersionId; if (status != STATUS_OK) return status; leveldb::WriteBatch batch; BumpNextRegistrationIdIfNeeded(registration.registration_id, &batch); BumpNextVersionIdIfNeeded(registration.version_id, &batch); PutUniqueOriginToBatch(registration.scope.GetOrigin(), &batch); DCHECK_EQ(AccumulateResourceSizeInBytes(resources), registration.resources_total_size_bytes) << "The total size in the registration must match the cumulative " << "sizes of the resources."; PutRegistrationDataToBatch(registration, &batch); batch.Put(CreateRegistrationIdToOriginKey(registration.registration_id), registration.scope.GetOrigin().spec()); // Used for avoiding multiple writes for the same resource id or url. std::set pushed_resources; std::set pushed_urls; for (std::vector::const_iterator itr = resources.begin(); itr != resources.end(); ++itr) { if (!itr->url.is_valid()) return STATUS_ERROR_FAILED; // Duplicated resource id or url should not exist. DCHECK(pushed_resources.insert(itr->resource_id).second); DCHECK(pushed_urls.insert(itr->url).second); PutResourceRecordToBatch(*itr, registration.version_id, &batch); // Delete a resource from the uncommitted list. batch.Delete(CreateResourceIdKey( kUncommittedResIdKeyPrefix, itr->resource_id)); // Delete from the purgeable list in case this version was once deleted. batch.Delete( CreateResourceIdKey(kPurgeableResIdKeyPrefix, itr->resource_id)); } // Retrieve a previous version to sweep purgeable resources. status = ReadRegistrationData(registration.registration_id, registration.scope.GetOrigin(), old_registration); if (status != STATUS_OK && status != STATUS_ERROR_NOT_FOUND) return status; if (status == STATUS_OK) { DCHECK_LT(old_registration->version_id, registration.version_id); status = DeleteResourceRecords( old_registration->version_id, newly_purgeable_resources, &batch); if (status != STATUS_OK) return status; // Currently resource sharing across versions and registrations is not // supported, so resource ids should not be overlapped between // |registration| and |old_registration|. std::set deleted_resources(newly_purgeable_resources->begin(), newly_purgeable_resources->end()); DCHECK(base::STLSetIntersection >( pushed_resources, deleted_resources).empty()); } return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateVersionToActive( int64 registration_id, const GURL& origin) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_ERROR_NOT_FOUND; if (status != STATUS_OK) return status; if (!origin.is_valid()) return STATUS_ERROR_FAILED; RegistrationData registration; status = ReadRegistrationData(registration_id, origin, ®istration); if (status != STATUS_OK) return status; registration.is_active = true; leveldb::WriteBatch batch; PutRegistrationDataToBatch(registration, &batch); return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpdateLastCheckTime( int64 registration_id, const GURL& origin, const base::Time& time) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_ERROR_NOT_FOUND; if (status != STATUS_OK) return status; if (!origin.is_valid()) return STATUS_ERROR_FAILED; RegistrationData registration; status = ReadRegistrationData(registration_id, origin, ®istration); if (status != STATUS_OK) return status; registration.last_update_check = time; leveldb::WriteBatch batch; PutRegistrationDataToBatch(registration, &batch); return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteRegistration( int64 registration_id, const GURL& origin, RegistrationData* deleted_version, std::vector* newly_purgeable_resources) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(deleted_version); deleted_version->version_id = kInvalidServiceWorkerVersionId; Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; if (!origin.is_valid()) return STATUS_ERROR_FAILED; leveldb::WriteBatch batch; // Remove |origin| from unique origins if a registration specified by // |registration_id| is the only one for |origin|. // TODO(nhiroki): Check the uniqueness by more efficient way. std::vector registrations; status = GetRegistrationsForOrigin(origin, ®istrations, nullptr); if (status != STATUS_OK) return status; if (registrations.size() == 1 && registrations[0].registration_id == registration_id) { batch.Delete(CreateUniqueOriginKey(origin)); } // Delete a registration specified by |registration_id|. batch.Delete(CreateRegistrationKey(registration_id, origin)); batch.Delete(CreateRegistrationIdToOriginKey(registration_id)); // Delete resource records and user data associated with the registration. for (const auto& registration : registrations) { if (registration.registration_id == registration_id) { *deleted_version = registration; status = DeleteResourceRecords( registration.version_id, newly_purgeable_resources, &batch); if (status != STATUS_OK) return status; status = DeleteUserDataForRegistration(registration_id, &batch); if (status != STATUS_OK) return status; break; } } return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadUserData( int64 registration_id, const std::string& user_data_name, std::string* user_data) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK_NE(kInvalidServiceWorkerRegistrationId, registration_id); DCHECK(!user_data_name.empty()); DCHECK(user_data); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_ERROR_NOT_FOUND; if (status != STATUS_OK) return status; const std::string key = CreateUserDataKey(registration_id, user_data_name); status = LevelDBStatusToStatus( db_->Get(leveldb::ReadOptions(), key, user_data)); HandleReadResult(FROM_HERE, status == STATUS_ERROR_NOT_FOUND ? STATUS_OK : status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteUserData( int64 registration_id, const GURL& origin, const std::string& user_data_name, const std::string& user_data) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK_NE(kInvalidServiceWorkerRegistrationId, registration_id); DCHECK(!user_data_name.empty()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_ERROR_NOT_FOUND; if (status != STATUS_OK) return status; // There should be the registration specified by |registration_id|. RegistrationData registration; status = ReadRegistrationData(registration_id, origin, ®istration); if (status != STATUS_OK) return status; leveldb::WriteBatch batch; batch.Put(CreateUserDataKey(registration_id, user_data_name), user_data); batch.Put(CreateHasUserDataKey(registration_id, user_data_name), ""); return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteUserData( int64 registration_id, const std::string& user_data_name) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK_NE(kInvalidServiceWorkerRegistrationId, registration_id); DCHECK(!user_data_name.empty()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; leveldb::WriteBatch batch; batch.Delete(CreateUserDataKey(registration_id, user_data_name)); batch.Delete(CreateHasUserDataKey(registration_id, user_data_name)); return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadUserDataForAllRegistrations( const std::string& user_data_name, std::vector>* user_data) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(user_data->empty()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; std::string key_prefix = CreateHasUserDataKeyPrefix(user_data_name); scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(key_prefix); itr->Valid(); itr->Next()) { status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); user_data->clear(); return status; } std::string registration_id_string; if (!RemovePrefix(itr->key().ToString(), key_prefix, ®istration_id_string)) { break; } int64 registration_id; status = ParseId(registration_id_string, ®istration_id); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); user_data->clear(); return status; } std::string value; status = LevelDBStatusToStatus( db_->Get(leveldb::ReadOptions(), CreateUserDataKey(registration_id, user_data_name), &value)); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); user_data->clear(); return status; } user_data->push_back(std::make_pair(registration_id, value)); } HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetUncommittedResourceIds(std::set* ids) { return ReadResourceIds(kUncommittedResIdKeyPrefix, ids); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteUncommittedResourceIds(const std::set& ids) { return WriteResourceIds(kUncommittedResIdKeyPrefix, ids); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ClearUncommittedResourceIds(const std::set& ids) { return DeleteResourceIds(kUncommittedResIdKeyPrefix, ids); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetPurgeableResourceIds(std::set* ids) { return ReadResourceIds(kPurgeableResIdKeyPrefix, ids); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WritePurgeableResourceIds(const std::set& ids) { return WriteResourceIds(kPurgeableResIdKeyPrefix, ids); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ClearPurgeableResourceIds(const std::set& ids) { return DeleteResourceIds(kPurgeableResIdKeyPrefix, ids); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::PurgeUncommittedResourceIds( const std::set& ids) { leveldb::WriteBatch batch; Status status = DeleteResourceIdsInBatch( kUncommittedResIdKeyPrefix, ids, &batch); if (status != STATUS_OK) return status; status = WriteResourceIdsInBatch(kPurgeableResIdKeyPrefix, ids, &batch); if (status != STATUS_OK) return status; return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteAllDataForOrigins( const std::set& origins, std::vector* newly_purgeable_resources) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; leveldb::WriteBatch batch; for (const GURL& origin : origins) { if (!origin.is_valid()) return STATUS_ERROR_FAILED; // Delete from the unique origin list. batch.Delete(CreateUniqueOriginKey(origin)); std::vector registrations; status = GetRegistrationsForOrigin(origin, ®istrations, nullptr); if (status != STATUS_OK) return status; // Delete registrations, resource records and user data. for (const RegistrationData& data : registrations) { batch.Delete(CreateRegistrationKey(data.registration_id, origin)); batch.Delete(CreateRegistrationIdToOriginKey(data.registration_id)); status = DeleteResourceRecords( data.version_id, newly_purgeable_resources, &batch); if (status != STATUS_OK) return status; status = DeleteUserDataForRegistration(data.registration_id, &batch); if (status != STATUS_OK) return status; } } return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DestroyDatabase() { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); Disable(FROM_HERE, STATUS_OK); if (IsDatabaseInMemory()) { env_.reset(); return STATUS_OK; } // Directly delete the database directory instead of leveldb::DestroyDB() // because the API does not delete the directory if there are unrelated files. // (https://code.google.com/p/chromium/issues/detail?id=468926#c24) Status status = base::DeleteFile(path_, true /* recursive */) ? STATUS_OK : STATUS_ERROR_FAILED; ServiceWorkerMetrics::RecordDestroyDatabaseResult(status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::LazyOpen( bool create_if_missing) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); // Do not try to open a database if we tried and failed once. if (state_ == DISABLED) return STATUS_ERROR_FAILED; if (IsOpen()) return STATUS_OK; if (!create_if_missing) { // Avoid opening a database if it does not exist at the |path_|. if (IsDatabaseInMemory() || !base::PathExists(path_) || base::IsDirectoryEmpty(path_)) { return STATUS_ERROR_NOT_FOUND; } } leveldb::Options options; options.create_if_missing = create_if_missing; options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue; if (IsDatabaseInMemory()) { env_.reset(leveldb::NewMemEnv(leveldb::Env::Default())); options.env = env_.get(); } else { options.env = g_service_worker_env.Pointer(); } leveldb::DB* db = NULL; Status status = LevelDBStatusToStatus( leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db)); HandleOpenResult(FROM_HERE, status); if (status != STATUS_OK) { DCHECK(!db); // TODO(nhiroki): Should we retry to open the database? return status; } db_.reset(db); int64 db_version; status = ReadDatabaseVersion(&db_version); if (status != STATUS_OK) return status; DCHECK_LE(0, db_version); if (db_version > 0 && db_version < kCurrentSchemaVersion) { switch (db_version) { case 1: status = UpgradeDatabaseSchemaFromV1ToV2(); if (status != STATUS_OK) return status; db_version = 2; // Intentionally fall-through to other version upgrade cases. } // Either the database got upgraded to the current schema version, or some // upgrade step failed which would have caused this method to abort. DCHECK_EQ(db_version, kCurrentSchemaVersion); } if (db_version > 0) state_ = INITIALIZED; return STATUS_OK; } bool ServiceWorkerDatabase::IsNewOrNonexistentDatabase( ServiceWorkerDatabase::Status status) { if (status == STATUS_ERROR_NOT_FOUND) return true; if (status == STATUS_OK && state_ == UNINITIALIZED) return true; return false; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::UpgradeDatabaseSchemaFromV1ToV2() { Status status = STATUS_OK; leveldb::WriteBatch batch; // Version 2 introduced REGID_TO_ORIGIN, add for all existing registrations. scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) { status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } std::string key; if (!RemovePrefix(itr->key().ToString(), kRegKeyPrefix, &key)) break; std::vector parts = base::SplitString(key, std::string(1, kKeySeparator), base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); if (parts.size() != 2) { status = STATUS_ERROR_CORRUPTED; HandleReadResult(FROM_HERE, status); return status; } int64 registration_id; status = ParseId(parts[1], ®istration_id); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } batch.Put(CreateRegistrationIdToOriginKey(registration_id), parts[0]); } // Update schema version manually instead of relying on WriteBatch to make // sure each upgrade step only updates it to the actually correct version. batch.Put(kDatabaseVersionKey, base::Int64ToString(2)); status = LevelDBStatusToStatus( db_->Write(leveldb::WriteOptions(), &batch)); HandleWriteResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadNextAvailableId( const char* id_key, int64* next_avail_id) { DCHECK(id_key); DCHECK(next_avail_id); std::string value; Status status = LevelDBStatusToStatus( db_->Get(leveldb::ReadOptions(), id_key, &value)); if (status == STATUS_ERROR_NOT_FOUND) { // Nobody has gotten the next resource id for |id_key|. *next_avail_id = 0; HandleReadResult(FROM_HERE, STATUS_OK); return STATUS_OK; } else if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } status = ParseId(value, next_avail_id); HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData( int64 registration_id, const GURL& origin, RegistrationData* registration) { DCHECK(registration); const std::string key = CreateRegistrationKey(registration_id, origin); std::string value; Status status = LevelDBStatusToStatus( db_->Get(leveldb::ReadOptions(), key, &value)); if (status != STATUS_OK) { HandleReadResult( FROM_HERE, status == STATUS_ERROR_NOT_FOUND ? STATUS_OK : status); return status; } status = ParseRegistrationData(value, registration); HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceRecords( int64 version_id, std::vector* resources) { DCHECK(resources->empty()); Status status = STATUS_OK; const std::string prefix = CreateResourceRecordKeyPrefix(version_id); scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(prefix); itr->Valid(); itr->Next()) { Status status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); resources->clear(); return status; } if (!RemovePrefix(itr->key().ToString(), prefix, NULL)) break; ResourceRecord resource; status = ParseResourceRecord(itr->value().ToString(), &resource); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); resources->clear(); return status; } resources->push_back(resource); } HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords( int64 version_id, std::vector* newly_purgeable_resources, leveldb::WriteBatch* batch) { DCHECK(batch); Status status = STATUS_OK; const std::string prefix = CreateResourceRecordKeyPrefix(version_id); scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(prefix); itr->Valid(); itr->Next()) { status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } const std::string key = itr->key().ToString(); std::string unprefixed; if (!RemovePrefix(key, prefix, &unprefixed)) break; int64 resource_id; status = ParseId(unprefixed, &resource_id); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } // Remove a resource record. batch->Delete(key); // Currently resource sharing across versions and registrations is not // supported, so we can purge this without caring about it. PutPurgeableResourceIdToBatch(resource_id, batch); newly_purgeable_resources->push_back(resource_id); } HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceIds( const char* id_key_prefix, std::set* ids) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(id_key_prefix); DCHECK(ids->empty()); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(id_key_prefix); itr->Valid(); itr->Next()) { status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); ids->clear(); return status; } std::string unprefixed; if (!RemovePrefix(itr->key().ToString(), id_key_prefix, &unprefixed)) break; int64 resource_id; status = ParseId(unprefixed, &resource_id); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); ids->clear(); return status; } ids->insert(resource_id); } HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIds( const char* id_key_prefix, const std::set& ids) { leveldb::WriteBatch batch; Status status = WriteResourceIdsInBatch(id_key_prefix, ids, &batch); if (status != STATUS_OK) return status; return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIdsInBatch( const char* id_key_prefix, const std::set& ids, leveldb::WriteBatch* batch) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(id_key_prefix); Status status = LazyOpen(true); if (status != STATUS_OK) return status; if (ids.empty()) return STATUS_OK; for (std::set::const_iterator itr = ids.begin(); itr != ids.end(); ++itr) { // Value should be empty. batch->Put(CreateResourceIdKey(id_key_prefix, *itr), ""); } // std::set is sorted, so the last element is the largest. BumpNextResourceIdIfNeeded(*ids.rbegin(), batch); return STATUS_OK; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIds( const char* id_key_prefix, const std::set& ids) { leveldb::WriteBatch batch; Status status = DeleteResourceIdsInBatch(id_key_prefix, ids, &batch); if (status != STATUS_OK) return status; return WriteBatch(&batch); } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIdsInBatch( const char* id_key_prefix, const std::set& ids, leveldb::WriteBatch* batch) { DCHECK(sequence_checker_.CalledOnValidSequencedThread()); DCHECK(id_key_prefix); Status status = LazyOpen(false); if (IsNewOrNonexistentDatabase(status)) return STATUS_OK; if (status != STATUS_OK) return status; for (std::set::const_iterator itr = ids.begin(); itr != ids.end(); ++itr) { batch->Delete(CreateResourceIdKey(id_key_prefix, *itr)); } return STATUS_OK; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteUserDataForRegistration( int64 registration_id, leveldb::WriteBatch* batch) { DCHECK(batch); Status status = STATUS_OK; const std::string prefix = CreateUserDataKeyPrefix(registration_id); scoped_ptr itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(prefix); itr->Valid(); itr->Next()) { status = LevelDBStatusToStatus(itr->status()); if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } const std::string key = itr->key().ToString(); std::string user_data_name; if (!RemovePrefix(key, prefix, &user_data_name)) break; batch->Delete(key); batch->Delete(CreateHasUserDataKey(registration_id, user_data_name)); } return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadDatabaseVersion( int64* db_version) { std::string value; Status status = LevelDBStatusToStatus( db_->Get(leveldb::ReadOptions(), kDatabaseVersionKey, &value)); if (status == STATUS_ERROR_NOT_FOUND) { // The database hasn't been initialized yet. *db_version = 0; HandleReadResult(FROM_HERE, STATUS_OK); return STATUS_OK; } if (status != STATUS_OK) { HandleReadResult(FROM_HERE, status); return status; } status = ParseDatabaseVersion(value, db_version); HandleReadResult(FROM_HERE, status); return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteBatch( leveldb::WriteBatch* batch) { DCHECK(batch); DCHECK_NE(DISABLED, state_); if (state_ == UNINITIALIZED) { // Write database default values. batch->Put(kDatabaseVersionKey, base::Int64ToString(kCurrentSchemaVersion)); if (!skip_writing_diskcache_migration_state_on_init_for_testing_) batch->Put(kDiskCacheMigrationNotNeededKey, kEmptyValue); state_ = INITIALIZED; } Status status = LevelDBStatusToStatus( db_->Write(leveldb::WriteOptions(), batch)); HandleWriteResult(FROM_HERE, status); return status; } void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded( int64 used_id, leveldb::WriteBatch* batch) { DCHECK(batch); if (next_avail_registration_id_ <= used_id) { next_avail_registration_id_ = used_id + 1; batch->Put(kNextRegIdKey, base::Int64ToString(next_avail_registration_id_)); } } void ServiceWorkerDatabase::BumpNextResourceIdIfNeeded( int64 used_id, leveldb::WriteBatch* batch) { DCHECK(batch); if (next_avail_resource_id_ <= used_id) { next_avail_resource_id_ = used_id + 1; batch->Put(kNextResIdKey, base::Int64ToString(next_avail_resource_id_)); } } void ServiceWorkerDatabase::BumpNextVersionIdIfNeeded( int64 used_id, leveldb::WriteBatch* batch) { DCHECK(batch); if (next_avail_version_id_ <= used_id) { next_avail_version_id_ = used_id + 1; batch->Put(kNextVerIdKey, base::Int64ToString(next_avail_version_id_)); } } bool ServiceWorkerDatabase::IsOpen() { return db_ != NULL; } void ServiceWorkerDatabase::Disable( const tracked_objects::Location& from_here, Status status) { if (status != STATUS_OK) { DLOG(ERROR) << "Failed at: " << from_here.ToString() << " with error: " << StatusToString(status); DLOG(ERROR) << "ServiceWorkerDatabase is disabled."; } state_ = DISABLED; db_.reset(); } void ServiceWorkerDatabase::HandleOpenResult( const tracked_objects::Location& from_here, Status status) { if (status != STATUS_OK) Disable(from_here, status); ServiceWorkerMetrics::CountOpenDatabaseResult(status); } void ServiceWorkerDatabase::HandleReadResult( const tracked_objects::Location& from_here, Status status) { if (status != STATUS_OK) Disable(from_here, status); ServiceWorkerMetrics::CountReadDatabaseResult(status); } void ServiceWorkerDatabase::HandleWriteResult( const tracked_objects::Location& from_here, Status status) { if (status != STATUS_OK) Disable(from_here, status); ServiceWorkerMetrics::CountWriteDatabaseResult(status); } bool ServiceWorkerDatabase::IsDatabaseInMemory() const { return path_.empty(); } } // namespace content