| // 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/threading/thread_task_runner_handle.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://www.w3.org/TR/2022/CRD-service-workers-20220712/#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://www.w3.org/TR/2022/CRD-service-workers-20220712/#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( |
| environment_settings()->context()->GetWindowOrWorkerGlobalScope(), |
| 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::Context* client = environment_settings()->context(); |
| ServiceWorkerContext* worker_context = client->service_worker_context(); |
| worker_context->message_loop()->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ServiceWorkerContext::MaybeResolveReadyPromiseSubSteps, |
| base::Unretained(worker_context), 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://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm |
| // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#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://www.w3.org/TR/2022/CRD-service-workers-20220712/#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( |
| environment_settings()->context()->GetWindowOrWorkerGlobalScope(), |
| promise)); |
| |
| // 2. Let client be this's service worker client. |
| web::Context* client = environment_settings()->context(); |
| // 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::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ServiceWorkerContext::StartRegister, |
| base::Unretained(client->service_worker_context()), |
| 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://www.w3.org/TR/2022/CRD-service-workers-20220712/#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 = |
| environment_settings() |
| ->context() |
| ->global_environment() |
| ->script_value_factory() |
| ->CreateInterfacePromise<scoped_refptr<ServiceWorkerRegistration>>(); |
| std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference( |
| new script::ValuePromiseWrappable::Reference( |
| environment_settings()->context()->GetWindowOrWorkerGlobalScope(), |
| promise)); |
| base::ThreadTaskRunnerHandle::Get()->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://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration |
| // 1. Let client be this's service worker client. |
| web::Context* client = environment_settings()->context(); |
| |
| // 2. Let storage key be the result of running obtain a storage key given |
| // client. |
| url::Origin storage_key = client->environment_settings()->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: |
| ServiceWorkerContext* worker_context = client->service_worker_context(); |
| DCHECK(worker_context); |
| worker_context->message_loop()->task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ServiceWorkerContext::GetRegistrationSubSteps, |
| base::Unretained(worker_context), storage_key, client_url, |
| client, std::move(promise_reference))); |
| } |
| |
| script::HandlePromiseSequenceWrappable |
| ServiceWorkerContainer::GetRegistrations() { |
| auto promise = environment_settings() |
| ->context() |
| ->global_environment() |
| ->script_value_factory() |
| ->CreateBasicPromise<script::SequenceWrappable>(); |
| std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference> |
| promise_reference(new script::ValuePromiseSequenceWrappable::Reference( |
| environment_settings()->context()->GetWindowOrWorkerGlobalScope(), |
| promise)); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ServiceWorkerContainer::GetRegistrationsTask, |
| base::Unretained(this), std::move(promise_reference))); |
| return promise; |
| } |
| |
| void ServiceWorkerContainer::GetRegistrationsTask( |
| std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference> |
| promise_reference) { |
| auto* client = environment_settings()->context(); |
| // https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistrations |
| ServiceWorkerContext* worker_context = |
| environment_settings()->context()->service_worker_context(); |
| url::Origin storage_key = environment_settings()->ObtainStorageKey(); |
| worker_context->message_loop()->task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&ServiceWorkerContext::GetRegistrationsSubSteps, |
| base::Unretained(worker_context), storage_key, |
| client, std::move(promise_reference))); |
| } |
| |
| void ServiceWorkerContainer::StartMessages() {} |
| |
| } // namespace worker |
| } // namespace cobalt |