diff options
4 files changed, 265 insertions, 118 deletions
diff --git a/content/browser/service_worker/service_worker_database.cc b/content/browser/service_worker/service_worker_database.cc index 73c4a31..f4da827 100644 --- a/content/browser/service_worker/service_worker_database.cc +++ b/content/browser/service_worker/service_worker_database.cc @@ -9,6 +9,7 @@ #include "base/file_util.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" @@ -77,6 +78,14 @@ const char kPurgeableResIdKeyPrefix[] = "PRES:"; const int64 kCurrentSchemaVersion = 1; +// For histogram. +const char kOpenResultHistogramLabel[] = + "ServiceWorker.Database.OpenResult"; +const char kReadResultHistogramLabel[] = + "ServiceWorker.Database.ReadResult"; +const char kWriteResultHistogramLabel[] = + "ServiceWorker.Database.WriteResult"; + bool RemovePrefix(const std::string& str, const std::string& prefix, std::string* out) { @@ -169,19 +178,51 @@ void PutPurgeableResourceIdToBatch(int64 resource_id, batch->Put(CreateResourceIdKey(kPurgeableResIdKeyPrefix, resource_id), ""); } -bool ParseRegistrationData(const std::string& serialized, - ServiceWorkerDatabase::RegistrationData* out) { +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 false; + 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()) { - return false; + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; } // Convert ServiceWorkerRegistrationData to RegistrationData. @@ -193,24 +234,25 @@ bool ParseRegistrationData(const std::string& serialized, out->has_fetch_handler = data.has_fetch_handler(); out->last_update_check = base::Time::FromInternalValue(data.last_update_check_time()); - return true; + return ServiceWorkerDatabase::STATUS_OK; } -bool ParseResourceRecord(const std::string& serialized, - ServiceWorkerDatabase::ResourceRecord* out) { +ServiceWorkerDatabase::Status ParseResourceRecord( + const std::string& serialized, + ServiceWorkerDatabase::ResourceRecord* out) { DCHECK(out); ServiceWorkerResourceRecord record; if (!record.ParseFromString(serialized)) - return false; + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; GURL url(record.url()); if (!url.is_valid()) - return false; + return ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED; // Convert ServiceWorkerResourceRecord to ResourceRecord. out->resource_id = record.resource_id(); out->url = url; - return true; + return ServiceWorkerDatabase::STATUS_OK; } ServiceWorkerDatabase::Status LevelDBStatusToStatus( @@ -219,12 +261,34 @@ ServiceWorkerDatabase::Status LevelDBStatusToStatus( 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; } +const char* 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"; +} + } // namespace ServiceWorkerDatabase::RegistrationData::RegistrationData() @@ -299,10 +363,11 @@ ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set<GURL>* origins) { scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(kUniqueOriginKey); itr->Valid(); itr->Next()) { - if (!itr->status().ok()) { - HandleError(FROM_HERE, itr->status()); + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); origins->clear(); - return LevelDBStatusToStatus(itr->status()); + return status; } std::string origin; @@ -310,7 +375,9 @@ ServiceWorkerDatabase::GetOriginsWithRegistrations(std::set<GURL>* origins) { break; origins->insert(GURL(origin)); } - return STATUS_OK; + + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin( @@ -331,24 +398,28 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetRegistrationsForOrigin( scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(prefix); itr->Valid(); itr->Next()) { - if (!itr->status().ok()) { - HandleError(FROM_HERE, itr->status()); + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); registrations->clear(); - return LevelDBStatusToStatus(itr->status()); + return status; } if (!RemovePrefix(itr->key().ToString(), prefix, NULL)) break; RegistrationData registration; - if (!ParseRegistrationData(itr->value().ToString(), ®istration)) { - HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); + status = ParseRegistrationData(itr->value().ToString(), ®istration); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); registrations->clear(); - return STATUS_ERROR_CORRUPTED; + return status; } registrations->push_back(registration); } - return STATUS_OK; + + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations( @@ -364,24 +435,28 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::GetAllRegistrations( scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(kRegKeyPrefix); itr->Valid(); itr->Next()) { - if (!itr->status().ok()) { - HandleError(FROM_HERE, itr->status()); + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); registrations->clear(); - return LevelDBStatusToStatus(itr->status()); + return status; } if (!RemovePrefix(itr->key().ToString(), kRegKeyPrefix, NULL)) break; RegistrationData registration; - if (!ParseRegistrationData(itr->value().ToString(), ®istration)) { - HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); + status = ParseRegistrationData(itr->value().ToString(), ®istration); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); registrations->clear(); - return STATUS_ERROR_CORRUPTED; + return status; } registrations->push_back(registration); } - return STATUS_OK; + + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistration( @@ -675,18 +750,18 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::LazyOpen( } leveldb::DB* db = NULL; - leveldb::Status db_status = - leveldb::DB::Open(options, path_.AsUTF8Unsafe(), &db); - if (!db_status.ok()) { + 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? - HandleError(FROM_HERE, db_status); - return LevelDBStatusToStatus(db_status); + return status; } db_.reset(db); int64 db_version; - Status status = ReadDatabaseVersion(&db_version); + status = ReadDatabaseVersion(&db_version); if (status != STATUS_OK) return status; DCHECK_LE(0, db_version); @@ -711,26 +786,21 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadNextAvailableId( DCHECK(next_avail_id); std::string value; - leveldb::Status status = db_->Get(leveldb::ReadOptions(), id_key, &value); - if (status.IsNotFound()) { + 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; } - if (!status.ok()) { - HandleError(FROM_HERE, status); - return LevelDBStatusToStatus(status); - } - - int64 parsed; - if (!base::StringToInt64(value, &parsed)) { - HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); - return STATUS_ERROR_CORRUPTED; - } - - *next_avail_id = parsed; - return STATUS_OK; + status = ParseId(value, next_avail_id); + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData( @@ -739,52 +809,54 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadRegistrationData( RegistrationData* registration) { DCHECK(registration); - std::string key = CreateRegistrationKey(registration_id, origin); - + const std::string key = CreateRegistrationKey(registration_id, origin); std::string value; - leveldb::Status status = db_->Get(leveldb::ReadOptions(), key, &value); - if (!status.ok()) { - if (!status.IsNotFound()) - HandleError(FROM_HERE, status); - return LevelDBStatusToStatus(status); - } - - RegistrationData parsed; - if (!ParseRegistrationData(value, &parsed)) { - HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); - return STATUS_ERROR_CORRUPTED; + 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; } - *registration = parsed; - return STATUS_OK; + status = ParseRegistrationData(value, registration); + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceRecords( int64 version_id, std::vector<ResourceRecord>* resources) { - DCHECK(resources); + DCHECK(resources->empty()); + + Status status = STATUS_OK; + const std::string prefix = CreateResourceRecordKeyPrefix(version_id); - std::string prefix = CreateResourceRecordKeyPrefix(version_id); scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(prefix); itr->Valid(); itr->Next()) { - if (!itr->status().ok()) { - HandleError(FROM_HERE, itr->status()); + Status status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); resources->clear(); - return LevelDBStatusToStatus(itr->status()); + return status; } if (!RemovePrefix(itr->key().ToString(), prefix, NULL)) break; ResourceRecord resource; - if (!ParseResourceRecord(itr->value().ToString(), &resource)) { - HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); + status = ParseResourceRecord(itr->value().ToString(), &resource); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); resources->clear(); - return STATUS_ERROR_CORRUPTED; + return status; } resources->push_back(resource); } - return STATUS_OK; + + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords( @@ -793,23 +865,27 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords( leveldb::WriteBatch* batch) { DCHECK(batch); - std::string prefix = CreateResourceRecordKeyPrefix(version_id); + Status status = STATUS_OK; + const std::string prefix = CreateResourceRecordKeyPrefix(version_id); + scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(prefix); itr->Valid(); itr->Next()) { - if (!itr->status().ok()) { - HandleError(FROM_HERE, itr->status()); - return LevelDBStatusToStatus(itr->status()); + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + return status; } - std::string key = itr->key().ToString(); + const std::string key = itr->key().ToString(); std::string unprefixed; if (!RemovePrefix(key, prefix, &unprefixed)) break; int64 resource_id; - if (!base::StringToInt64(unprefixed, &resource_id)) { - HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); - return STATUS_ERROR_CORRUPTED; + status = ParseId(unprefixed, &resource_id); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + return status; } // Remove a resource record. @@ -820,7 +896,9 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceRecords( PutPurgeableResourceIdToBatch(resource_id, batch); newly_purgeable_resources->push_back(resource_id); } - return STATUS_OK; + + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceIds( @@ -838,10 +916,11 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceIds( scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions())); for (itr->Seek(id_key_prefix); itr->Valid(); itr->Next()) { - if (!itr->status().ok()) { - HandleError(FROM_HERE, itr->status()); + status = LevelDBStatusToStatus(itr->status()); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); ids->clear(); - return LevelDBStatusToStatus(itr->status()); + return status; } std::string unprefixed; @@ -849,14 +928,17 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadResourceIds( break; int64 resource_id; - if (!base::StringToInt64(unprefixed, &resource_id)) { - HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); + status = ParseId(unprefixed, &resource_id); + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); ids->clear(); - return STATUS_ERROR_CORRUPTED; + return status; } ids->insert(resource_id); } - return STATUS_OK; + + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteResourceIds( @@ -921,32 +1003,23 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::DeleteResourceIdsInBatch( ServiceWorkerDatabase::Status ServiceWorkerDatabase::ReadDatabaseVersion( int64* db_version) { std::string value; - leveldb::Status status = - db_->Get(leveldb::ReadOptions(), kDatabaseVersionKey, &value); - if (status.IsNotFound()) { + 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.ok()) { - HandleError(FROM_HERE, status); - return LevelDBStatusToStatus(status); - } - int64 parsed; - if (!base::StringToInt64(value, &parsed)) { - HandleError(FROM_HERE, leveldb::Status::Corruption("failed to parse")); - return STATUS_ERROR_CORRUPTED; - } - - const int kFirstValidVersion = 1; - if (parsed < kFirstValidVersion || kCurrentSchemaVersion < parsed) { - HandleError(FROM_HERE, leveldb::Status::Corruption("invalid DB version")); - return STATUS_ERROR_CORRUPTED; + if (status != STATUS_OK) { + HandleReadResult(FROM_HERE, status); + return status; } - *db_version = parsed; - return STATUS_OK; + status = ParseDatabaseVersion(value, db_version); + HandleReadResult(FROM_HERE, status); + return status; } ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteBatch( @@ -960,10 +1033,10 @@ ServiceWorkerDatabase::Status ServiceWorkerDatabase::WriteBatch( state_ = INITIALIZED; } - leveldb::Status status = db_->Write(leveldb::WriteOptions(), batch); - if (!status.ok()) - HandleError(FROM_HERE, status); - return LevelDBStatusToStatus(status); + Status status = LevelDBStatusToStatus( + db_->Write(leveldb::WriteOptions(), batch)); + HandleWriteResult(FROM_HERE, status); + return status; } void ServiceWorkerDatabase::BumpNextRegistrationIdIfNeeded( @@ -988,14 +1061,44 @@ bool ServiceWorkerDatabase::IsOpen() { return db_ != NULL; } -void ServiceWorkerDatabase::HandleError( +void ServiceWorkerDatabase::Disable( const tracked_objects::Location& from_here, - const leveldb::Status& status) { - // TODO(nhiroki): Add an UMA histogram. + Status status) { DLOG(ERROR) << "Failed at: " << from_here.ToString() - << " with error: " << status.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 != ServiceWorkerDatabase::STATUS_OK) + Disable(from_here, status); + UMA_HISTOGRAM_ENUMERATION(kOpenResultHistogramLabel, + status, + ServiceWorkerDatabase::STATUS_ERROR_MAX); +} + +void ServiceWorkerDatabase::HandleReadResult( + const tracked_objects::Location& from_here, + Status status) { + if (status != ServiceWorkerDatabase::STATUS_OK) + Disable(from_here, status); + UMA_HISTOGRAM_ENUMERATION(kReadResultHistogramLabel, + status, + ServiceWorkerDatabase::STATUS_ERROR_MAX); +} + +void ServiceWorkerDatabase::HandleWriteResult( + const tracked_objects::Location& from_here, + Status status) { + if (status != ServiceWorkerDatabase::STATUS_OK) + Disable(from_here, status); + UMA_HISTOGRAM_ENUMERATION(kWriteResultHistogramLabel, + status, + ServiceWorkerDatabase::STATUS_ERROR_MAX); +} + } // namespace content diff --git a/content/browser/service_worker/service_worker_database.h b/content/browser/service_worker/service_worker_database.h index 660b4c7..7b2a68d 100644 --- a/content/browser/service_worker/service_worker_database.h +++ b/content/browser/service_worker/service_worker_database.h @@ -39,11 +39,14 @@ class CONTENT_EXPORT ServiceWorkerDatabase { explicit ServiceWorkerDatabase(const base::FilePath& path); ~ServiceWorkerDatabase(); + // Used in UMA. A new value must be appended only. enum Status { STATUS_OK, STATUS_ERROR_NOT_FOUND, + STATUS_ERROR_IO_ERROR, STATUS_ERROR_CORRUPTED, STATUS_ERROR_FAILED, + STATUS_ERROR_MAX, }; struct CONTENT_EXPORT RegistrationData { @@ -273,9 +276,18 @@ class CONTENT_EXPORT ServiceWorkerDatabase { bool IsOpen(); - void HandleError( + void Disable( const tracked_objects::Location& from_here, - const leveldb::Status& status); + Status status); + void HandleOpenResult( + const tracked_objects::Location& from_here, + Status status); + void HandleReadResult( + const tracked_objects::Location& from_here, + Status status); + void HandleWriteResult( + const tracked_objects::Location& from_here, + Status status); base::FilePath path_; scoped_ptr<leveldb::Env> env_; diff --git a/content/browser/service_worker/service_worker_storage.cc b/content/browser/service_worker/service_worker_storage.cc index 7ec25a4..0e40d94 100644 --- a/content/browser/service_worker/service_worker_storage.cc +++ b/content/browser/service_worker/service_worker_storage.cc @@ -64,6 +64,8 @@ ServiceWorkerStatusCode DatabaseStatusToStatusCode( return SERVICE_WORKER_OK; case ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND: return SERVICE_WORKER_ERROR_NOT_FOUND; + case ServiceWorkerDatabase::STATUS_ERROR_MAX: + NOTREACHED(); default: return SERVICE_WORKER_ERROR_FAILED; } diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index c2fd7d3..3eece60 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -24517,6 +24517,28 @@ Therefore, the affected-histogram name has to have at least one dot in it. </summary> </histogram> +<histogram name="ServiceWorker.Database.OpenResult" + enum="ServiceWorkerDatabaseStatus"> + <owner>nhiroki@chromium.org</owner> + <summary> + Records result of opening a database for ServiceWorkerDatabase. + </summary> +</histogram> + +<histogram name="ServiceWorker.Database.ReadResult" + enum="ServiceWorkerDatabaseStatus"> + <owner>nhiroki@chromium.org</owner> + <summary>Records result of read operations in ServiceWorkerDatabase.</summary> +</histogram> + +<histogram name="ServiceWorker.Database.WriteResult" + enum="ServiceWorkerDatabaseStatus"> + <owner>nhiroki@chromium.org</owner> + <summary> + Records result of write operations in ServiceWorkerDatabase. + </summary> +</histogram> + <histogram name="Settings.DefaultSearchProvider" enum="OmniboxSearchEngine"> <obsolete> Deprecated in Chrome 30. Use Search.DefaultSearchProviderType instead. @@ -42782,6 +42804,14 @@ Therefore, the affected-histogram name has to have at least one dot in it. <int value="10" label="SERVICE_UTILITY_SEMANTIC_CAPS_FAILED"/> </enum> +<enum name="ServiceWorkerDatabaseStatus" type="int"> + <int value="0" label="OK"/> + <int value="1" label="Not Found Error"/> + <int value="2" label="IO Error"/> + <int value="3" label="Corruption Error"/> + <int value="4" label="Operation Error"/> +</enum> + <enum name="SessionStartupPref" type="int"> <int value="0" label="Open home page (unused)"/> <int value="1" label="Continue from last opened pages"/> |