blob: fc81821f4e3f2b7c213989b06c87b40b2293b8cc [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/worker/service_worker_persistent_settings.h"
#include <list>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/synchronization/lock.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/persistent_storage/persistent_settings.h"
#include "cobalt/script/exception_message.h"
#include "cobalt/script/promise.h"
#include "cobalt/script/script_value.h"
#include "cobalt/web/cache_utils.h"
#include "cobalt/worker/service_worker_context.h"
#include "cobalt/worker/service_worker_jobs.h"
#include "cobalt/worker/service_worker_registration_object.h"
#include "cobalt/worker/service_worker_update_via_cache.h"
#include "cobalt/worker/worker_consts.h"
#include "cobalt/worker/worker_global_scope.h"
#include "net/base/completion_once_callback.h"
#include "net/disk_cache/cobalt/resource_type.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace cobalt {
namespace worker {
namespace {
// ServiceWorkerRegistrationMap persistent settings keys.
const char kSettingsKeyList[] = "key_list";
// ServiceWorkerRegistrationObject persistent settings keys.
const char kSettingsStorageKeyKey[] = "storage_key";
const char kSettingsScopeStringKey[] = "scope_string";
const char kSettingsScopeUrlKey[] = "scope_url";
const char kSettingsUpdateViaCacheModeKey[] = "update_via_cache_mode";
const char kSettingsWaitingWorkerKey[] = "waiting_worker";
const char kSettingsActiveWorkerKey[] = "active_worker";
const char kSettingsLastUpdateCheckTimeKey[] = "last_update_check_time";
// ServicerWorkerObject persistent settings keys.
const char kSettingsOptionsNameKey[] = "options_name";
const char kSettingsScriptUrlKey[] = "script_url";
const char kSettingsScriptResourceMapScriptUrlsKey[] =
"script_resource_map_script_urls";
const char kSettingsSetOfUsedScriptsKey[] = "set_of_used_scripts";
const char kSettingsSkipWaitingKey[] = "skip_waiting";
const char kSettingsClassicScriptsImportedKey[] = "classic_scripts_imported";
const char kSettingsRawHeadersKey[] = "raw_headers";
bool CheckPersistentValue(
std::string key_string, std::string settings_key,
base::flat_map<std::string, std::unique_ptr<base::Value>>& dict,
base::Value::Type type) {
if (!dict.contains(settings_key)) {
DLOG(INFO) << "Key: " << key_string << " does not contain " << settings_key;
return false;
} else if (!(dict[settings_key]->type() == type)) {
DLOG(INFO) << "Key: " << key_string << " " << settings_key
<< " is of type: " << dict[settings_key]->type()
<< ", but expected type is: " << type;
return false;
}
return true;
}
} // namespace
ServiceWorkerPersistentSettings::ServiceWorkerPersistentSettings(
const Options& options)
: options_(options) {
persistent_settings_.reset(new cobalt::persistent_storage::PersistentSettings(
WorkerConsts::kSettingsJson));
persistent_settings_->ValidatePersistentSettings();
DCHECK(persistent_settings_);
}
void ServiceWorkerPersistentSettings::ReadServiceWorkerRegistrationMapSettings(
std::map<RegistrationMapKey,
scoped_refptr<ServiceWorkerRegistrationObject>>&
registration_map) {
std::vector<base::Value> key_list =
persistent_settings_->GetPersistentSettingAsList(kSettingsKeyList);
std::set<std::string> unverified_key_set;
for (auto& key : key_list) {
if (key.is_string()) {
unverified_key_set.insert(key.GetString());
}
}
for (auto& key_string : unverified_key_set) {
auto dict =
persistent_settings_->GetPersistentSettingAsDictionary(key_string);
if (dict.empty()) {
DLOG(INFO) << "Key: " << key_string << " does not exist in "
<< WorkerConsts::kSettingsJson;
continue;
}
if (!CheckPersistentValue(key_string, kSettingsStorageKeyKey, dict,
base::Value::Type::STRING))
continue;
url::Origin storage_key =
url::Origin::Create(GURL(dict[kSettingsStorageKeyKey]->GetString()));
// Only add persisted workers to the registration_map
// if their storage_key matches the origin of the initial_url.
if (!storage_key.IsSameOriginWith(url::Origin::Create(options_.url))) {
continue;
}
if (!CheckPersistentValue(key_string, kSettingsScopeUrlKey, dict,
base::Value::Type::STRING))
continue;
GURL scope(dict[kSettingsScopeUrlKey]->GetString());
if (!CheckPersistentValue(key_string, kSettingsUpdateViaCacheModeKey, dict,
base::Value::Type::INTEGER))
continue;
ServiceWorkerUpdateViaCache update_via_cache =
static_cast<ServiceWorkerUpdateViaCache>(
dict[kSettingsUpdateViaCacheModeKey]->GetInt());
if (!CheckPersistentValue(key_string, kSettingsScopeStringKey, dict,
base::Value::Type::STRING))
continue;
std::string scope_string(dict[kSettingsScopeStringKey]->GetString());
RegistrationMapKey key(storage_key, scope_string);
scoped_refptr<ServiceWorkerRegistrationObject> registration(
new ServiceWorkerRegistrationObject(storage_key, scope,
update_via_cache));
auto worker_key = kSettingsWaitingWorkerKey;
if (!CheckPersistentValue(key_string, worker_key, dict,
base::Value::Type::DICTIONARY)) {
worker_key = kSettingsActiveWorkerKey;
if (!CheckPersistentValue(key_string, worker_key, dict,
base::Value::Type::DICTIONARY))
continue;
}
if (!ReadServiceWorkerObjectSettings(
registration, key_string, std::move(dict[worker_key]), worker_key))
continue;
if (CheckPersistentValue(key_string, kSettingsLastUpdateCheckTimeKey, dict,
base::Value::Type::STRING)) {
int64_t last_update_check_time =
std::atol(dict[kSettingsLastUpdateCheckTimeKey]->GetString().c_str());
registration->set_last_update_check_time(
base::Time::FromDeltaSinceWindowsEpoch(
base::TimeDelta::FromMicroseconds(last_update_check_time)));
}
key_set_.insert(key_string);
registration_map.insert(std::make_pair(key, registration));
registration->set_is_persisted(true);
options_.service_worker_context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ServiceWorkerContext::Activate,
base::Unretained(options_.service_worker_context),
registration));
auto job = options_.service_worker_context->jobs()->CreateJobWithoutPromise(
ServiceWorkerJobs::JobType::kUpdate, storage_key, scope,
registration->waiting_worker()->script_url());
options_.service_worker_context->message_loop()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ServiceWorkerJobs::ScheduleJob,
base::Unretained(
options_.service_worker_context->jobs()),
std::move(job)));
}
}
bool ServiceWorkerPersistentSettings::ReadServiceWorkerObjectSettings(
scoped_refptr<ServiceWorkerRegistrationObject> registration,
std::string key_string, std::unique_ptr<base::Value> value_dict,
std::string worker_key_string) {
base::Value* options_name_value = value_dict->FindKeyOfType(
kSettingsOptionsNameKey, base::Value::Type::STRING);
if (options_name_value == nullptr) return false;
ServiceWorkerObject::Options options(options_name_value->GetString(),
options_.web_settings,
options_.network_module, registration);
options.web_options.platform_info = options_.platform_info;
options.web_options.service_worker_context = options_.service_worker_context;
scoped_refptr<ServiceWorkerObject> worker(new ServiceWorkerObject(options));
base::Value* script_url_value = value_dict->FindKeyOfType(
kSettingsScriptUrlKey, base::Value::Type::STRING);
if (script_url_value == nullptr) return false;
worker->set_script_url(GURL(script_url_value->GetString()));
base::Value* skip_waiting_value = value_dict->FindKeyOfType(
kSettingsSkipWaitingKey, base::Value::Type::BOOLEAN);
if (skip_waiting_value == nullptr) return false;
if (skip_waiting_value->GetBool()) worker->set_skip_waiting();
base::Value* classic_scripts_imported_value = value_dict->FindKeyOfType(
kSettingsClassicScriptsImportedKey, base::Value::Type::BOOLEAN);
if (classic_scripts_imported_value == nullptr) return false;
if (classic_scripts_imported_value->GetBool())
worker->set_classic_scripts_imported();
worker->set_start_status(nullptr);
base::Value* used_scripts_value = value_dict->FindKeyOfType(
kSettingsSetOfUsedScriptsKey, base::Value::Type::LIST);
if (used_scripts_value == nullptr) return false;
std::vector<base::Value> used_scripts_list = used_scripts_value->TakeList();
for (int i = 0; i < used_scripts_list.size(); i++) {
auto script_value = std::move(used_scripts_list[i]);
if (script_value.is_string()) {
worker->AppendToSetOfUsedScripts(GURL(script_value.GetString()));
}
}
base::Value* script_urls_value = value_dict->FindKeyOfType(
kSettingsScriptResourceMapScriptUrlsKey, base::Value::Type::LIST);
if (script_urls_value == nullptr) return false;
std::vector<base::Value> script_urls_list = script_urls_value->TakeList();
ScriptResourceMap script_resource_map;
for (int i = 0; i < script_urls_list.size(); i++) {
auto script_url_value = std::move(script_urls_list[i]);
if (script_url_value.is_string()) {
auto script_url_string = script_url_value.GetString();
auto script_url = GURL(script_url_string);
std::unique_ptr<std::vector<uint8_t>> data =
cobalt::cache::Cache::GetInstance()->Retrieve(
disk_cache::ResourceType::kServiceWorkerScript,
web::cache_utils::GetKey(key_string + script_url_string));
if (data == nullptr) {
return false;
}
auto script_resource = ScriptResource(std::make_unique<std::string>(
std::string(data->begin(), data->end())));
if (script_url == worker->script_url()) {
// Get the persistent headers for the ServiceWorkerObject script_url_.
// This is used in ServiceWorkerObject::Initialize().
base::Value* raw_header_value = value_dict->FindKeyOfType(
kSettingsRawHeadersKey, base::Value::Type::STRING);
if (raw_header_value == nullptr) return false;
const scoped_refptr<net::HttpResponseHeaders> headers =
scoped_refptr<net::HttpResponseHeaders>(
new net::HttpResponseHeaders(raw_header_value->GetString()));
script_resource.headers = headers;
}
auto result = script_resource_map.insert(
std::make_pair(script_url, std::move(script_resource)));
DCHECK(result.second);
}
}
if (script_resource_map.size() == 0) {
return false;
}
worker->set_script_resource_map(std::move(script_resource_map));
registration->set_waiting_worker(worker);
return true;
}
void ServiceWorkerPersistentSettings::
WriteServiceWorkerRegistrationObjectSettings(
RegistrationMapKey key,
scoped_refptr<ServiceWorkerRegistrationObject> registration) {
auto key_string = key.first.GetURL().spec() + key.second;
base::flat_map<std::string, std::unique_ptr<base::Value>> dict;
// https://w3c.github.io/ServiceWorker/#user-agent-shutdown
// An installing worker does not persist, but is discarded.
auto waiting_worker = registration->waiting_worker();
auto active_worker = registration->active_worker();
if (waiting_worker == nullptr && active_worker == nullptr) {
// If the installing worker was the only service worker for the service
// worker registration, the service worker registration is discarded.
RemoveServiceWorkerRegistrationObjectSettings(key);
return;
}
if (waiting_worker) {
// A waiting worker promotes to an active worker. This will be handled
// upon restart.
dict.try_emplace(
kSettingsWaitingWorkerKey,
WriteServiceWorkerObjectSettings(key_string, waiting_worker));
} else {
dict.try_emplace(kSettingsActiveWorkerKey, WriteServiceWorkerObjectSettings(
key_string, active_worker));
}
// Add key_string to the registered keys and write to persistent settings.
key_set_.insert(key_string);
std::vector<base::Value> key_list;
for (auto& key : key_set_) {
key_list.emplace_back(key);
}
persistent_settings_->SetPersistentSetting(
kSettingsKeyList, std::make_unique<base::Value>(std::move(key_list)));
// Persist ServiceWorkerRegistrationObject's fields.
dict.try_emplace(kSettingsStorageKeyKey,
std::make_unique<base::Value>(
registration->storage_key().GetURL().spec()));
dict.try_emplace(kSettingsScopeStringKey,
std::make_unique<base::Value>(key.second));
dict.try_emplace(kSettingsScopeUrlKey, std::make_unique<base::Value>(
registration->scope_url().spec()));
dict.try_emplace(
kSettingsUpdateViaCacheModeKey,
std::make_unique<base::Value>(registration->update_via_cache_mode()));
dict.try_emplace(kSettingsLastUpdateCheckTimeKey,
std::make_unique<base::Value>(
std::to_string(registration->last_update_check_time()
.ToDeltaSinceWindowsEpoch()
.InMicroseconds())));
persistent_settings_->SetPersistentSetting(
key_string, std::make_unique<base::Value>(dict));
}
std::unique_ptr<base::Value>
ServiceWorkerPersistentSettings::WriteServiceWorkerObjectSettings(
std::string registration_key_string,
const scoped_refptr<ServiceWorkerObject>& service_worker_object) {
base::flat_map<std::string, std::unique_ptr<base::Value>> dict;
DCHECK(service_worker_object);
dict.try_emplace(
kSettingsOptionsNameKey,
std::make_unique<base::Value>(service_worker_object->options_name()));
dict.try_emplace(kSettingsScriptUrlKey,
std::make_unique<base::Value>(
service_worker_object->script_url().spec()));
dict.try_emplace(
kSettingsSkipWaitingKey,
std::make_unique<base::Value>(service_worker_object->skip_waiting()));
dict.try_emplace(kSettingsClassicScriptsImportedKey,
std::make_unique<base::Value>(
service_worker_object->classic_scripts_imported()));
// Persist set_of_used_scripts as a List.
base::Value set_of_used_scripts_value(base::Value::Type::LIST);
for (auto script_url : service_worker_object->set_of_used_scripts()) {
set_of_used_scripts_value.GetList().push_back(
base::Value(script_url.spec()));
}
dict.try_emplace(
kSettingsSetOfUsedScriptsKey,
std::make_unique<base::Value>(std::move(set_of_used_scripts_value)));
// Persist the script_resource_map script urls as a List.
base::Value script_urls_value(base::Value::Type::LIST);
for (auto const& script_resource :
service_worker_object->script_resource_map()) {
std::string script_url_string = script_resource.first.spec();
script_urls_value.GetList().push_back(base::Value(script_url_string));
// Use Cache::Store to persist the script resource.
std::string resource = *(script_resource.second.content.get());
std::vector<uint8_t> data(resource.begin(), resource.end());
cobalt::cache::Cache::GetInstance()->Store(
disk_cache::ResourceType::kServiceWorkerScript,
web::cache_utils::GetKey(registration_key_string + script_url_string),
data,
/* metadata */ base::nullopt);
if (script_url_string == service_worker_object->script_url().spec()) {
// Persist the raw headers from the ServiceWorkerObject script_url_
// ScriptResource headers.
dict.try_emplace(kSettingsRawHeadersKey,
std::make_unique<base::Value>(
script_resource.second.headers->raw_headers()));
}
}
dict.try_emplace(kSettingsScriptResourceMapScriptUrlsKey,
std::make_unique<base::Value>(std::move(script_urls_value)));
return std::move(std::make_unique<base::Value>(dict));
}
void ServiceWorkerPersistentSettings::
RemoveServiceWorkerRegistrationObjectSettings(RegistrationMapKey key) {
auto key_string = key.first.GetURL().spec() + key.second;
if (key_set_.find(key_string) == key_set_.end()) {
// The key does not exist in PersistentSettings.
return;
}
// Remove the worker script_resource_map from the Cache.
RemoveServiceWorkerObjectSettings(key_string);
// Remove registration key string.
key_set_.erase(key_string);
std::vector<base::Value> key_list;
for (auto& key : key_set_) {
key_list.emplace_back(key);
}
persistent_settings_->SetPersistentSetting(
kSettingsKeyList, std::make_unique<base::Value>(std::move(key_list)));
// Remove the registration dictionary.
persistent_settings_->RemovePersistentSetting(key_string);
}
void ServiceWorkerPersistentSettings::RemoveServiceWorkerObjectSettings(
std::string key_string) {
auto dict =
persistent_settings_->GetPersistentSettingAsDictionary(key_string);
if (dict.empty()) return;
std::vector<std::string> worker_keys{kSettingsWaitingWorkerKey,
kSettingsActiveWorkerKey};
for (std::string worker_key : worker_keys) {
if (!CheckPersistentValue(key_string, worker_key, dict,
base::Value::Type::DICTIONARY))
continue;
auto worker_dict = std::move(dict[worker_key]);
base::Value* script_urls_value = worker_dict->FindKeyOfType(
kSettingsScriptResourceMapScriptUrlsKey, base::Value::Type::LIST);
if (script_urls_value == nullptr) return;
std::vector<base::Value> script_urls_list = script_urls_value->TakeList();
for (int i = 0; i < script_urls_list.size(); i++) {
auto script_url_value = std::move(script_urls_list[i]);
if (script_url_value.is_string()) {
auto script_url_string = script_url_value.GetString();
cobalt::cache::Cache::GetInstance()->Delete(
disk_cache::ResourceType::kServiceWorkerScript,
web::cache_utils::GetKey(key_string + script_url_string));
}
}
}
}
void ServiceWorkerPersistentSettings::RemoveAll() {
for (auto& key : key_set_) {
persistent_settings_->RemovePersistentSetting(key);
}
}
void ServiceWorkerPersistentSettings::DeleteAll(base::OnceClosure closure) {
persistent_settings_->DeletePersistentSettings(std::move(closure));
}
} // namespace worker
} // namespace cobalt