blob: 43fb13a2937c9d1f6498449f9d7c6405c05e5301 [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_container.h"
#include <string>
#include <utility>
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/optional.h"
#include "base/task_runner.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/dom/dom_settings.h"
#include "cobalt/script/promise.h"
#include "cobalt/script/script_value_factory.h"
#include "cobalt/web/context.h"
#include "cobalt/web/dom_exception.h"
#include "cobalt/web/environment_settings.h"
#include "cobalt/worker/registration_options.h"
#include "cobalt/worker/service_worker_update_via_cache.h"
#include "cobalt/worker/worker_type.h"
#include "url/gurl.h"
namespace cobalt {
namespace worker {
ServiceWorkerContainer::ServiceWorkerContainer(
script::EnvironmentSettings* settings)
: web::EventTarget(settings) {}
scoped_refptr<ServiceWorker> ServiceWorkerContainer::controller() {
// Algorithm for controller:
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-controller
// 1. Let client be this's service worker client.
web::EnvironmentSettings* client = environment_settings();
// 2. If client’s active service worker is null, then return null.
if (!client->context()->active_service_worker()) {
return scoped_refptr<ServiceWorker>();
}
// 3. Return the result of getting the service worker object that represents
// client’s active service worker in this's relevant settings object.
return client->context()->GetServiceWorker(
client->context()->active_service_worker());
}
script::HandlePromiseWrappable ServiceWorkerContainer::ready() {
// Algorithm for ready attribute:
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-ready
// 1. If this's ready promise is null, then set this's ready promise to a new
// promise.
if (!promise_reference_) {
ready_promise_ = environment_settings()
->context()
->global_environment()
->script_value_factory()
->CreateInterfacePromise<
scoped_refptr<ServiceWorkerRegistration>>();
promise_reference_.reset(
new script::ValuePromiseWrappable::Reference(this, ready_promise_));
}
// 2. Let readyPromise be this's ready promise.
script::HandlePromiseWrappable ready_promise(ready_promise_);
// 3. If readyPromise is pending, run the following substeps in parallel:
if (ready_promise->State() == script::PromiseState::kPending) {
// 3.1. Let client by this's service worker client.
web::EnvironmentSettings* client = environment_settings();
worker::ServiceWorkerJobs* jobs = client->context()->service_worker_jobs();
jobs->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ServiceWorkerJobs::MaybeResolveReadyPromiseSubSteps,
base::Unretained(jobs), client));
}
// 4. Return readyPromise.
return ready_promise;
}
void ServiceWorkerContainer::MaybeResolveReadyPromise(
ServiceWorkerRegistrationObject* registration) {
// This implements resolving of the ready promise for the Activate algorithm
// (steps 7.1-7.3) as well as for the ready attribute (step 3.3).
// https://w3c.github.io/ServiceWorker/#activation-algorithm
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-ready
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerContainer::MaybeResolveReadyPromise()");
DCHECK_EQ(base::MessageLoop::current(),
environment_settings()->context()->message_loop());
if (!registration || !registration->active_worker()) return;
if (!promise_reference_) return;
if (ready_promise_->State() != script::PromiseState::kPending) return;
auto registration_object =
environment_settings()->context()->LookupServiceWorkerRegistration(
registration);
if (registration_object) {
DCHECK(registration_object->active());
ready_promise_->Resolve(registration_object);
promise_reference_.reset();
}
}
script::HandlePromiseWrappable ServiceWorkerContainer::Register(
const std::string& url) {
RegistrationOptions options;
return ServiceWorkerContainer::Register(url, options);
}
script::HandlePromiseWrappable ServiceWorkerContainer::Register(
const std::string& url, const RegistrationOptions& options) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerContainer::Register()");
DCHECK_EQ(base::MessageLoop::current(),
environment_settings()->context()->message_loop());
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-register
// 1. Let p be a promise.
script::HandlePromiseWrappable promise =
environment_settings()
->context()
->global_environment()
->script_value_factory()
->CreateInterfacePromise<scoped_refptr<ServiceWorkerRegistration>>();
std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference(
new script::ValuePromiseWrappable::Reference(this, promise));
// 2. Let client be this's service worker client.
web::EnvironmentSettings* client = environment_settings();
// 3. Let scriptURL be the result of parsing scriptURL with this's
// relevant settings object’s API base URL.
const GURL& base_url = environment_settings()->base_url();
GURL script_url = base_url.Resolve(url);
// 4. Let scopeURL be null.
base::Optional<GURL> scope_url;
// 5. If options["scope"] exists, set scopeURL to the result of parsing
// options["scope"] with this's relevant settings object’s API base URL.
if (options.has_scope()) {
scope_url = base_url.Resolve(options.scope());
}
// 6. Invoke Start Register with scopeURL, scriptURL, p, client, client’s
// creation URL, options["type"], and options["updateViaCache"].
base::MessageLoop::current()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&ServiceWorkerJobs::StartRegister,
base::Unretained(client->context()->service_worker_jobs()),
scope_url, script_url, std::move(promise_reference),
client, options.type(), options.update_via_cache()));
// 7. Return p.
return promise;
}
script::HandlePromiseWrappable ServiceWorkerContainer::GetRegistration(
const std::string& url) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerContainer::GetRegistration()");
DCHECK_EQ(base::MessageLoop::current(),
environment_settings()->context()->message_loop());
// Algorithm for 'ServiceWorkerContainer.getRegistration()':
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
// Let promise be a new promise.
// Perform the rest of the steps in a task, because the promise has to be
// returned before we can safely reject or resolve it.
auto promise =
base::polymorphic_downcast<web::EnvironmentSettings*>(
environment_settings())
->context()
->global_environment()
->script_value_factory()
->CreateInterfacePromise<scoped_refptr<ServiceWorkerRegistration>>();
std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference(
new script::ValuePromiseWrappable::Reference(this, promise));
base::MessageLoop::current()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ServiceWorkerContainer::GetRegistrationTask,
base::Unretained(this), url,
std::move(promise_reference)));
// 9. Return promise.
return promise;
}
void ServiceWorkerContainer::GetRegistrationTask(
const std::string& url,
std::unique_ptr<script::ValuePromiseWrappable::Reference>
promise_reference) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerContainer::GetRegistrationTask()");
DCHECK_EQ(base::MessageLoop::current(),
environment_settings()->context()->message_loop());
// Algorithm for 'ServiceWorkerContainer.getRegistration()':
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
// 1. Let client be this's service worker client.
web::EnvironmentSettings* client = environment_settings();
// 2. Let storage key be the result of running obtain a storage key given
// client.
url::Origin storage_key = client->ObtainStorageKey();
// 3. Let clientURL be the result of parsing clientURL with this's relevant
// settings object’s API base URL.
// TODO(b/234659851): Investigate whether this behaves as expected for empty
// url values.
const GURL& base_url = environment_settings()->base_url();
GURL client_url = base_url.Resolve(url);
// 4. If clientURL is failure, return a promise rejected with a TypeError.
if (client_url.is_empty()) {
promise_reference->value().Reject(script::kTypeError);
return;
}
// 5. Set clientURL’s fragment to null.
url::Replacements<char> replacements;
replacements.ClearRef();
client_url = client_url.ReplaceComponents(replacements);
DCHECK(!client_url.has_ref() || client_url.ref().empty());
// 6. If the origin of clientURL is not client’s origin, return a promise
// rejected with a "SecurityError" web::DOMException.
if (client_url.GetOrigin() != base_url.GetOrigin()) {
promise_reference->value().Reject(
new web::DOMException(web::DOMException::kSecurityErr));
return;
}
// 7. Let promise be a new promise.
// 8. Run the following substeps in parallel:
worker::ServiceWorkerJobs* jobs = client->context()->service_worker_jobs();
DCHECK(jobs);
jobs->message_loop()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ServiceWorkerJobs::GetRegistrationSubSteps,
base::Unretained(jobs), storage_key, client_url,
client, std::move(promise_reference)));
}
script::HandlePromiseSequenceWrappable
ServiceWorkerContainer::GetRegistrations() {
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistrations
// 1. Let client be this's service worker client.
// 2. Let promise be a new promise.
// 3. Run the following steps in parallel:
// 1. Let registrations be a new list.
// 2. For each scope → registration of scope to registration map:
// 1. If the origin of the result of parsing scope is the same as
// client’s origin, then append registration to registrations.
// 3. Queue a task on promise’s relevant settings object's responsible
// event loop, using the DOM manipulation task source, to run the
// following steps:
// 1. Let registrationObjects be a new list.
// 2. For each registration of registrations:
// 1. Let registrationObj be the result of getting the service worker
// registration object that represents registration in promise’s
// relevant settings object.
// 2. Append registrationObj to registrationObjects.
// 3. Resolve promise with a new frozen array of registrationObjects in
// promise’s relevant Realm.
// 4. Return promise.
// TODO(b/235531652): Implement getRegistrations().
auto promise = environment_settings()
->context()
->global_environment()
->script_value_factory()
->CreateBasicPromise<script::SequenceWrappable>();
return promise;
}
void ServiceWorkerContainer::StartMessages() {}
} // namespace worker
} // namespace cobalt