From 75f3f7e5adf7c321e4e6b201ac91b2c335d6831f Mon Sep 17 00:00:00 2001 From: aee <117306596+aee-google@users.noreply.github.com> Date: Fri, 23 Jun 2023 09:50:44 -0700 Subject: [PATCH] Moving service worker job related code to its own file. (#628) b/286597506 Change-Id: I0ca3ac56d3f1e1acdc6fd2cd8faac34a463b8d61 (cherry picked from commit 6e280c8f020bb5cccdbbf070f60805ccdbfea86c) --- cobalt/browser/browser_module.cc | 4 +- cobalt/browser/service_worker_registry.cc | 15 +- cobalt/browser/service_worker_registry.h | 6 +- cobalt/layout_tests/web_platform_tests.cc | 4 +- cobalt/web/agent.cc | 22 +- cobalt/web/agent.h | 4 +- cobalt/web/context.h | 4 +- cobalt/web/testing/stub_web_context.h | 2 +- cobalt/worker/BUILD.gn | 2 + cobalt/worker/clients.cc | 34 +- cobalt/worker/dedicated_worker.cc | 4 +- cobalt/worker/extendable_event.cc | 9 +- cobalt/worker/extendable_event.h | 2 +- cobalt/worker/service_worker.cc | 15 +- cobalt/worker/service_worker_container.cc | 41 +- cobalt/worker/service_worker_container.h | 2 +- cobalt/worker/service_worker_context.cc | 1724 +++++++++++++++++ cobalt/worker/service_worker_context.h | 221 +++ cobalt/worker/service_worker_global_scope.cc | 26 +- cobalt/worker/service_worker_jobs.cc | 1047 +--------- cobalt/worker/service_worker_jobs.h | 91 +- .../service_worker_persistent_settings.cc | 21 +- .../service_worker_persistent_settings.h | 6 +- cobalt/worker/service_worker_registration.cc | 11 +- .../worker/service_worker_registration_map.cc | 8 +- .../worker/service_worker_registration_map.h | 7 +- 26 files changed, 2153 insertions(+), 1179 deletions(-) create mode 100644 cobalt/worker/service_worker_context.cc create mode 100644 cobalt/worker/service_worker_context.h diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc index f2c7ffea41f6..e8f1c28a36db 100644 --- a/cobalt/browser/browser_module.cc +++ b/cobalt/browser/browser_module.cc @@ -629,8 +629,8 @@ void BrowserModule::Navigate(const GURL& url_reference) { options.web_options.web_settings = &web_settings_; options.web_options.network_module = network_module_; - options.web_options.service_worker_jobs = - service_worker_registry_->service_worker_jobs(); + options.web_options.service_worker_context = + service_worker_registry_->service_worker_context(); options.web_options.platform_info = platform_info_.get(); web_module_.reset(new WebModule("MainWebModule")); // Wait for service worker to start if one exists. diff --git a/cobalt/browser/service_worker_registry.cc b/cobalt/browser/service_worker_registry.cc index 23a1a1c20578..f5dd4684f1ac 100644 --- a/cobalt/browser/service_worker_registry.cc +++ b/cobalt/browser/service_worker_registry.cc @@ -22,7 +22,6 @@ #include "base/threading/thread.h" #include "base/trace_event/trace_event.h" #include "cobalt/network/network_module.h" -#include "cobalt/worker/service_worker_jobs.h" namespace cobalt { namespace browser { @@ -34,7 +33,7 @@ void SignalWaitableEvent(base::WaitableEvent* event) { event->Signal(); } void ServiceWorkerRegistry::WillDestroyCurrentMessageLoop() { // Clear all member variables allocated from the thread. - service_worker_jobs_.reset(); + service_worker_context_.reset(); } ServiceWorkerRegistry::ServiceWorkerRegistry( @@ -75,20 +74,20 @@ ServiceWorkerRegistry::~ServiceWorkerRegistry() { destruction_observer_added_.Wait(); DCHECK_NE(thread_.message_loop(), base::MessageLoop::current()); thread_.Stop(); - DCHECK(!service_worker_jobs_); + DCHECK(!service_worker_context_); } void ServiceWorkerRegistry::EnsureServiceWorkerStarted( const url::Origin& storage_key, const GURL& client_url, base::WaitableEvent* done_event) { - service_worker_jobs()->EnsureServiceWorkerStarted(storage_key, client_url, - done_event); + service_worker_context()->EnsureServiceWorkerStarted(storage_key, client_url, + done_event); } -worker::ServiceWorkerJobs* ServiceWorkerRegistry::service_worker_jobs() { +worker::ServiceWorkerContext* ServiceWorkerRegistry::service_worker_context() { // Ensure that the thread had a chance to allocate the object. destruction_observer_added_.Wait(); - return service_worker_jobs_.get(); + return service_worker_context_.get(); } void ServiceWorkerRegistry::Initialize( @@ -96,7 +95,7 @@ void ServiceWorkerRegistry::Initialize( web::UserAgentPlatformInfo* platform_info, const GURL& url) { TRACE_EVENT0("cobalt::browser", "ServiceWorkerRegistry::Initialize()"); DCHECK_EQ(base::MessageLoop::current(), message_loop()); - service_worker_jobs_.reset(new worker::ServiceWorkerJobs( + service_worker_context_.reset(new worker::ServiceWorkerContext( web_settings, network_module, platform_info, message_loop(), url)); } diff --git a/cobalt/browser/service_worker_registry.h b/cobalt/browser/service_worker_registry.h index 8c37083a91d5..5b571183c821 100644 --- a/cobalt/browser/service_worker_registry.h +++ b/cobalt/browser/service_worker_registry.h @@ -22,7 +22,7 @@ #include "base/threading/thread.h" #include "cobalt/network/network_module.h" #include "cobalt/web/web_settings.h" -#include "cobalt/worker/service_worker_jobs.h" +#include "cobalt/worker/service_worker_context.h" namespace cobalt { namespace browser { @@ -48,7 +48,7 @@ class ServiceWorkerRegistry : public base::MessageLoop::DestructionObserver { const GURL& client_url, base::WaitableEvent* done_event); - worker::ServiceWorkerJobs* service_worker_jobs(); + worker::ServiceWorkerContext* service_worker_context(); private: // Called by the constructor to perform any other initialization required on @@ -69,7 +69,7 @@ class ServiceWorkerRegistry : public base::MessageLoop::DestructionObserver { base::WaitableEvent::ResetPolicy::MANUAL, base::WaitableEvent::InitialState::NOT_SIGNALED}; - std::unique_ptr service_worker_jobs_; + std::unique_ptr service_worker_context_; }; } // namespace browser diff --git a/cobalt/layout_tests/web_platform_tests.cc b/cobalt/layout_tests/web_platform_tests.cc index 05c237645be9..455c37eeabae 100644 --- a/cobalt/layout_tests/web_platform_tests.cc +++ b/cobalt/layout_tests/web_platform_tests.cc @@ -232,8 +232,8 @@ std::string RunWebPlatformTest(const GURL& url, bool* got_results) { web_module_options.web_options.web_settings = &web_settings; web_module_options.web_options.network_module = &network_module; - web_module_options.web_options.service_worker_jobs = - service_worker_registry->service_worker_jobs(); + web_module_options.web_options.service_worker_context = + service_worker_registry->service_worker_context(); web_module_options.web_options.platform_info = platform_info.get(); // Prepare a slot for our results to be placed when ready. diff --git a/cobalt/web/agent.cc b/cobalt/web/agent.cc index d81242b98085..4efb0a47268c 100644 --- a/cobalt/web/agent.cc +++ b/cobalt/web/agent.cc @@ -38,7 +38,7 @@ #include "cobalt/web/url.h" #include "cobalt/web/window_or_worker_global_scope.h" #include "cobalt/worker/service_worker.h" -#include "cobalt/worker/service_worker_jobs.h" +#include "cobalt/worker/service_worker_context.h" #include "cobalt/worker/service_worker_object.h" #include "cobalt/worker/service_worker_registration.h" #include "cobalt/worker/service_worker_registration_object.h" @@ -91,8 +91,8 @@ class Impl : public Context { DCHECK(fetcher_factory_); return fetcher_factory_->network_module(); } - worker::ServiceWorkerJobs* service_worker_jobs() const final { - return service_worker_jobs_; + worker::ServiceWorkerContext* service_worker_context() const final { + return service_worker_context_; } const std::string& name() const final { return name_; }; @@ -217,7 +217,7 @@ class Impl : public Context { std::map> service_worker_object_map_; - worker::ServiceWorkerJobs* service_worker_jobs_; + worker::ServiceWorkerContext* service_worker_context_; const web::UserAgentPlatformInfo* platform_info_; // https://html.spec.whatwg.org/multipage/webappapis.html#concept-environment-active-service-worker @@ -250,7 +250,7 @@ void LogScriptError(const base::SourceLocation& source_location, Impl::Impl(const std::string& name, const Agent::Options& options) : name_(name), web_settings_(options.web_settings) { TRACE_EVENT0("cobalt::web", "Agent::Impl::Impl()"); - service_worker_jobs_ = options.service_worker_jobs; + service_worker_context_ = options.service_worker_context; platform_info_ = options.platform_info; blob_registry_.reset(new Blob::Registry); @@ -352,11 +352,9 @@ void Impl::SetupFinished() { } #endif - if (service_worker_jobs_) { - service_worker_jobs_->RegisterWebContext(this); - } - if (service_worker_jobs_) { - service_worker_jobs_->SetActiveWorker(environment_settings_.get()); + if (service_worker_context_) { + service_worker_context_->RegisterWebContext(this); + service_worker_context_->SetActiveWorker(environment_settings_.get()); } } @@ -533,8 +531,8 @@ void Agent::Stop() { DCHECK(message_loop()); DCHECK(thread_.IsRunning()); - if (context() && context()->service_worker_jobs()) { - context()->service_worker_jobs()->UnregisterWebContext(context()); + if (context() && context()->service_worker_context()) { + context()->service_worker_context()->UnregisterWebContext(context()); } // Ensure that the destruction observer got added before stopping the thread. diff --git a/cobalt/web/agent.h b/cobalt/web/agent.h index d57d52644c6e..091cee0f02fe 100644 --- a/cobalt/web/agent.h +++ b/cobalt/web/agent.h @@ -34,7 +34,7 @@ namespace cobalt { namespace worker { -class ServiceWorkerJobs; +class ServiceWorkerContext; } namespace web { @@ -69,7 +69,7 @@ class Agent : public base::MessageLoop::DestructionObserver { base::Callback*)> read_cache_callback; - worker::ServiceWorkerJobs* service_worker_jobs = nullptr; + worker::ServiceWorkerContext* service_worker_context = nullptr; const UserAgentPlatformInfo* platform_info = nullptr; }; diff --git a/cobalt/web/context.h b/cobalt/web/context.h index 3fdf42cb996c..b8fc450c494a 100644 --- a/cobalt/web/context.h +++ b/cobalt/web/context.h @@ -36,7 +36,7 @@ namespace worker { class ServiceWorkerRegistration; class ServiceWorkerRegistrationObject; class ServiceWorker; -class ServiceWorkerJobs; +class ServiceWorkerContext; class ServiceWorkerObject; } // namespace worker namespace web { @@ -65,7 +65,7 @@ class Context { virtual Blob::Registry* blob_registry() const = 0; virtual web::WebSettings* web_settings() const = 0; virtual network::NetworkModule* network_module() const = 0; - virtual worker::ServiceWorkerJobs* service_worker_jobs() const = 0; + virtual worker::ServiceWorkerContext* service_worker_context() const = 0; virtual const std::string& name() const = 0; virtual void SetupEnvironmentSettings(EnvironmentSettings* settings) = 0; diff --git a/cobalt/web/testing/stub_web_context.h b/cobalt/web/testing/stub_web_context.h index 87268f004a5c..487101b5a761 100644 --- a/cobalt/web/testing/stub_web_context.h +++ b/cobalt/web/testing/stub_web_context.h @@ -110,7 +110,7 @@ class StubWebContext final : public Context { return network_module_.get(); } - worker::ServiceWorkerJobs* service_worker_jobs() const final { + worker::ServiceWorkerContext* service_worker_context() const final { NOTREACHED(); return nullptr; } diff --git a/cobalt/worker/BUILD.gn b/cobalt/worker/BUILD.gn index 24d814712a67..23aeaa109388 100644 --- a/cobalt/worker/BUILD.gn +++ b/cobalt/worker/BUILD.gn @@ -40,6 +40,8 @@ static_library("worker") { "service_worker_consts.h", "service_worker_container.cc", "service_worker_container.h", + "service_worker_context.cc", + "service_worker_context.h", "service_worker_global_scope.cc", "service_worker_global_scope.h", "service_worker_jobs.cc", diff --git a/cobalt/worker/clients.cc b/cobalt/worker/clients.cc index c78e12ac9d5d..c32966d3b289 100644 --- a/cobalt/worker/clients.cc +++ b/cobalt/worker/clients.cc @@ -33,8 +33,8 @@ #include "cobalt/web/dom_exception.h" #include "cobalt/web/environment_settings.h" #include "cobalt/worker/client.h" +#include "cobalt/worker/service_worker_context.h" #include "cobalt/worker/service_worker_global_scope.h" -#include "cobalt/worker/service_worker_jobs.h" #include "cobalt/worker/service_worker_object.h" namespace cobalt { @@ -72,12 +72,13 @@ script::HandlePromiseWrappable Clients::Get(const std::string& id) { settings_->context()->GetWindowOrWorkerGlobalScope(), promise)); // 2. Run these substeps in parallel: - ServiceWorkerJobs* jobs = settings_->context()->service_worker_jobs(); - DCHECK(jobs); - jobs->message_loop()->task_runner()->PostTask( + ServiceWorkerContext* context = + settings_->context()->service_worker_context(); + DCHECK(context); + context->message_loop()->task_runner()->PostTask( FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::ClientsGetSubSteps, - base::Unretained(jobs), + base::BindOnce(&ServiceWorkerContext::ClientsGetSubSteps, + base::Unretained(context), base::Unretained(settings_->context()), base::Unretained(GetAssociatedServiceWorker(settings_)), std::move(promise_reference), id)); @@ -101,12 +102,13 @@ script::HandlePromiseSequenceWrappable Clients::MatchAll( promise_reference(new script::ValuePromiseSequenceWrappable::Reference( settings_->context()->GetWindowOrWorkerGlobalScope(), promise)); // 2. Run the following steps in parallel: - ServiceWorkerJobs* jobs = settings_->context()->service_worker_jobs(); - DCHECK(jobs); - jobs->message_loop()->task_runner()->PostTask( + ServiceWorkerContext* context = + settings_->context()->service_worker_context(); + DCHECK(context); + context->message_loop()->task_runner()->PostTask( FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::ClientsMatchAllSubSteps, - base::Unretained(jobs), + base::BindOnce(&ServiceWorkerContext::ClientsMatchAllSubSteps, + base::Unretained(context), base::Unretained(settings_->context()), base::Unretained(GetAssociatedServiceWorker(settings_)), std::move(promise_reference), @@ -156,11 +158,13 @@ script::HandlePromiseVoid Clients::Claim() { service_worker->state() == kServiceWorkerStateActivating); // 3. Run the following substeps in parallel: - ServiceWorkerJobs* jobs = settings_->context()->service_worker_jobs(); - DCHECK(jobs); - jobs->message_loop()->task_runner()->PostTask( + ServiceWorkerContext* context = + settings_->context()->service_worker_context(); + DCHECK(context); + context->message_loop()->task_runner()->PostTask( FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::ClaimSubSteps, base::Unretained(jobs), + base::BindOnce(&ServiceWorkerContext::ClaimSubSteps, + base::Unretained(context), base::Unretained(settings_->context()), base::Unretained(GetAssociatedServiceWorker(settings_)), std::move(promise_reference))); diff --git a/cobalt/worker/dedicated_worker.cc b/cobalt/worker/dedicated_worker.cc index 624dbec884cf..489a987628ef 100644 --- a/cobalt/worker/dedicated_worker.cc +++ b/cobalt/worker/dedicated_worker.cc @@ -97,8 +97,8 @@ void DedicatedWorker::Initialize(script::ExceptionState* exception_state) { options.outside_event_target = this; options.outside_port = outside_port_.get(); options.options = worker_options_; - options.web_options.service_worker_jobs = - options.outside_context->service_worker_jobs(); + options.web_options.service_worker_context = + options.outside_context->service_worker_context(); // Store the current source location as the construction location, to be used // in the error event if worker loading of initialization fails. auto stack_trace = diff --git a/cobalt/worker/extendable_event.cc b/cobalt/worker/extendable_event.cc index 87d48cba9c16..5b285dc51ea9 100644 --- a/cobalt/worker/extendable_event.cc +++ b/cobalt/worker/extendable_event.cc @@ -72,14 +72,15 @@ void ExtendableEvent::StateChange( web::Context* context = base::polymorphic_downcast(settings) ->context(); - ServiceWorkerJobs* jobs = context->service_worker_jobs(); - DCHECK(jobs); + ServiceWorkerContext* worker_context = context->service_worker_context(); + DCHECK(worker_context); // 5.2.1. Let registration be the current global object's associated // service worker's containing service worker registration. - jobs->message_loop()->task_runner()->PostTask( + worker_context->message_loop()->task_runner()->PostTask( FROM_HERE, base::BindOnce( - &ServiceWorkerJobs::WaitUntilSubSteps, base::Unretained(jobs), + &ServiceWorkerContext::WaitUntilSubSteps, + base::Unretained(worker_context), base::Unretained(context->GetWindowOrWorkerGlobalScope() ->AsServiceWorker() ->service_worker_object() diff --git a/cobalt/worker/extendable_event.h b/cobalt/worker/extendable_event.h index fbd8136b9547..5864eb0efd1f 100644 --- a/cobalt/worker/extendable_event.h +++ b/cobalt/worker/extendable_event.h @@ -35,8 +35,8 @@ #include "cobalt/web/event.h" #include "cobalt/web/window_or_worker_global_scope.h" #include "cobalt/worker/extendable_event_init.h" +#include "cobalt/worker/service_worker_context.h" #include "cobalt/worker/service_worker_global_scope.h" -#include "cobalt/worker/service_worker_jobs.h" #include "cobalt/worker/service_worker_object.h" #include "cobalt/worker/service_worker_registration_object.h" #include "v8/include/v8.h" diff --git a/cobalt/worker/service_worker.cc b/cobalt/worker/service_worker.cc index b8fad61e9ce2..49cd00529db6 100644 --- a/cobalt/worker/service_worker.cc +++ b/cobalt/worker/service_worker.cc @@ -24,8 +24,8 @@ #include "cobalt/web/environment_settings_helper.h" #include "cobalt/web/event_target.h" #include "cobalt/web/message_port.h" +#include "cobalt/worker/service_worker_context.h" #include "cobalt/worker/service_worker_global_scope.h" -#include "cobalt/worker/service_worker_jobs.h" #include "cobalt/worker/service_worker_object.h" #include "cobalt/worker/service_worker_state.h" @@ -56,13 +56,14 @@ void ServiceWorker::PostMessage(const script::ValueHandleHolder& message) { // "message" and serviceWorker is true, then return. if (service_worker->ShouldSkipEvent(base::Tokens::message())) return; // 6. Run these substeps in parallel: - ServiceWorkerJobs* jobs = - incumbent_settings->context()->service_worker_jobs(); - DCHECK(jobs); - jobs->message_loop()->task_runner()->PostTask( + ServiceWorkerContext* worker_context = + incumbent_settings->context()->service_worker_context(); + DCHECK(worker_context); + worker_context->message_loop()->task_runner()->PostTask( FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::ServiceWorkerPostMessageSubSteps, - base::Unretained(jobs), base::Unretained(service_worker), + base::BindOnce(&ServiceWorkerContext::ServiceWorkerPostMessageSubSteps, + base::Unretained(worker_context), + base::Unretained(service_worker), base::Unretained(incumbent_settings->context()), std::move(structured_clone))); } diff --git a/cobalt/worker/service_worker_container.cc b/cobalt/worker/service_worker_container.cc index b392f5cc6949..268481dea71c 100644 --- a/cobalt/worker/service_worker_container.cc +++ b/cobalt/worker/service_worker_container.cc @@ -80,11 +80,11 @@ script::HandlePromiseWrappable ServiceWorkerContainer::ready() { if (ready_promise->State() == script::PromiseState::kPending) { // 3.1. Let client by this's service worker client. web::Context* client = environment_settings()->context(); - worker::ServiceWorkerJobs* jobs = client->service_worker_jobs(); - jobs->message_loop()->task_runner()->PostTask( + ServiceWorkerContext* worker_context = client->service_worker_context(); + worker_context->message_loop()->task_runner()->PostTask( FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::MaybeResolveReadyPromiseSubSteps, - base::Unretained(jobs), client)); + base::BindOnce(&ServiceWorkerContext::MaybeResolveReadyPromiseSubSteps, + base::Unretained(worker_context), client)); } // 4. Return readyPromise. return ready_promise; @@ -155,10 +155,10 @@ script::HandlePromiseWrappable ServiceWorkerContainer::Register( // creation URL, options["type"], and options["updateViaCache"]. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::StartRegister, - base::Unretained(client->service_worker_jobs()), scope_url, - script_url, std::move(promise_reference), client, - options.type(), options.update_via_cache())); + 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; } @@ -237,12 +237,13 @@ void ServiceWorkerContainer::GetRegistrationTask( // 7. Let promise be a new promise. // 8. Run the following substeps in parallel: - worker::ServiceWorkerJobs* jobs = client->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))); + 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 @@ -268,13 +269,13 @@ void ServiceWorkerContainer::GetRegistrationsTask( promise_reference) { auto* client = environment_settings()->context(); // https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistrations - worker::ServiceWorkerJobs* jobs = - environment_settings()->context()->service_worker_jobs(); + ServiceWorkerContext* worker_context = + environment_settings()->context()->service_worker_context(); url::Origin storage_key = environment_settings()->ObtainStorageKey(); - jobs->message_loop()->task_runner()->PostTask( - FROM_HERE, base::BindOnce(&ServiceWorkerJobs::GetRegistrationsSubSteps, - base::Unretained(jobs), storage_key, client, - std::move(promise_reference))); + 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() {} diff --git a/cobalt/worker/service_worker_container.h b/cobalt/worker/service_worker_container.h index 94679d6b5d90..3fc52ab8b10e 100644 --- a/cobalt/worker/service_worker_container.h +++ b/cobalt/worker/service_worker_container.h @@ -26,7 +26,7 @@ #include "cobalt/web/event_target.h" #include "cobalt/web/event_target_listener_info.h" #include "cobalt/worker/registration_options.h" -#include "cobalt/worker/service_worker_jobs.h" +#include "cobalt/worker/service_worker_context.h" #include "cobalt/worker/service_worker_registration.h" namespace cobalt { diff --git a/cobalt/worker/service_worker_context.cc b/cobalt/worker/service_worker_context.cc new file mode 100644 index 000000000000..62cf6faa24cc --- /dev/null +++ b/cobalt/worker/service_worker_context.cc @@ -0,0 +1,1724 @@ +// Copyright 2023 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_context.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/message_loop/message_loop_current.h" +#include "base/time/time.h" +#include "base/trace_event/trace_event.h" +#include "cobalt/web/dom_exception.h" +#include "cobalt/web/environment_settings.h" +#include "cobalt/worker/extendable_event.h" +#include "cobalt/worker/extendable_message_event.h" +#include "cobalt/worker/service_worker_container.h" +#include "cobalt/worker/service_worker_jobs.h" +#include "cobalt/worker/window_client.h" + +namespace cobalt { +namespace worker { + +namespace { + +const base::TimeDelta kWaitForAsynchronousExtensionsTimeout = + base::TimeDelta::FromSeconds(3); + +const base::TimeDelta kShutdownWaitTimeoutSecs = + base::TimeDelta::FromSeconds(5); + +bool PathContainsEscapedSlash(const GURL& url) { + const std::string path = url.path(); + return (path.find("%2f") != std::string::npos || + path.find("%2F") != std::string::npos || + path.find("%5c") != std::string::npos || + path.find("%5C") != std::string::npos); +} + +void ResolveGetClientPromise( + web::Context* client, web::Context* worker_context, + std::unique_ptr + promise_reference) { + TRACE_EVENT0("cobalt::worker", "ResolveGetClientPromise()"); + // Algorithm for Resolve Get Client Promise: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-get-client-promise + + // 1. If client is an environment settings object, then: + // 1.1. If client is not a secure context, queue a task to reject promise with + // a "SecurityError" DOMException, on promise’s relevant settings + // object's responsible event loop using the DOM manipulation task + // source, and abort these steps. + // 2. Else: + // 2.1. If client’s creation URL is not a potentially trustworthy URL, queue + // a task to reject promise with a "SecurityError" DOMException, on + // promise’s relevant settings object's responsible event loop using the + // DOM manipulation task source, and abort these steps. + // In production, Cobalt requires https, therefore all clients are secure + // contexts. + + // 3. If client is an environment settings object and is not a window client, + // then: + if (!client->GetWindowOrWorkerGlobalScope()->IsWindow()) { + // 3.1. Let clientObject be the result of running Create Client algorithm + // with client as the argument. + scoped_refptr client_object = + Client::Create(client->environment_settings()); + + // 3.2. Queue a task to resolve promise with clientObject, on promise’s + // relevant settings object's responsible event loop using the DOM + // manipulation task source, and abort these steps. + worker_context->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](std::unique_ptr + promise_reference, + scoped_refptr client_object) { + TRACE_EVENT0("cobalt::worker", + "ResolveGetClientPromise() Resolve"); + promise_reference->value().Resolve(client_object); + }, + std::move(promise_reference), client_object)); + return; + } + // 4. Else: + // 4.1. Let browsingContext be null. + // 4.2. If client is an environment settings object, set browsingContext to + // client’s global object's browsing context. + // 4.3. Else, set browsingContext to client’s target browsing context. + // Note: Cobalt does not implement a distinction between environments and + // environment settings objects. + // 4.4. Queue a task to run the following steps on browsingContext’s event + // loop using the user interaction task source: + // Note: The task below does not currently perform any actual + // functionality in the client context. It is included however to help future + // implementation for fetching values for WindowClient properties, with + // similar logic existing in ClientsMatchAllSubSteps. + client->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](web::Context* client, web::Context* worker_context, + std::unique_ptr + promise_reference) { + // 4.4.1. Let frameType be the result of running Get Frame Type with + // browsingContext. + // Cobalt does not support nested or auxiliary browsing contexts. + // 4.4.2. Let visibilityState be browsingContext’s active document's + // visibilityState attribute value. + // 4.4.3. Let focusState be the result of running the has focus + // steps with browsingContext’s active document as the + // argument. + // Handled in the WindowData constructor. + std::unique_ptr window_data( + new WindowData(client->environment_settings())); + + // 4.4.4. Let ancestorOriginsList be the empty list. + // 4.4.5. If client is a window client, set ancestorOriginsList to + // browsingContext’s active document's relevant global + // object's Location object’s ancestor origins list's + // associated list. + // Cobalt does not implement Location.ancestorOrigins. + + // 4.4.6. Queue a task to run the following steps on promise’s + // relevant settings object's responsible event loop using + // the DOM manipulation task source: + worker_context->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](std::unique_ptr + promise_reference, + std::unique_ptr window_data) { + // 4.4.6.1. If client’s discarded flag is set, resolve + // promise with undefined and abort these + // steps. + // 4.4.6.2. Let windowClient be the result of running + // Create Window Client with client, + // frameType, visibilityState, focusState, + // and ancestorOriginsList. + scoped_refptr window_client = + WindowClient::Create(*window_data); + // 4.4.6.3. Resolve promise with windowClient. + promise_reference->value().Resolve(window_client); + }, + std::move(promise_reference), std::move(window_data))); + }, + client, worker_context, std::move(promise_reference))); + DCHECK_EQ(nullptr, promise_reference.get()); +} + +} // namespace + +ServiceWorkerContext::ServiceWorkerContext( + web::WebSettings* web_settings, network::NetworkModule* network_module, + web::UserAgentPlatformInfo* platform_info, base::MessageLoop* message_loop, + const GURL& url) + : message_loop_(message_loop) { + DCHECK_EQ(message_loop_, base::MessageLoop::current()); + jobs_ = + std::make_unique(this, network_module, message_loop); + + ServiceWorkerPersistentSettings::Options options(web_settings, network_module, + platform_info, this, url); + scope_to_registration_map_.reset(new ServiceWorkerRegistrationMap(options)); + DCHECK(scope_to_registration_map_); +} + +ServiceWorkerContext::~ServiceWorkerContext() { + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + scope_to_registration_map_->HandleUserAgentShutdown(this); + scope_to_registration_map_->AbortAllActive(); + scope_to_registration_map_.reset(); + if (!web_context_registrations_.empty()) { + // Abort any Service Workers that remain. + for (auto& context : web_context_registrations_) { + DCHECK(context); + if (context->GetWindowOrWorkerGlobalScope()->IsServiceWorker()) { + ServiceWorkerGlobalScope* service_worker = + context->GetWindowOrWorkerGlobalScope()->AsServiceWorker(); + if (service_worker && service_worker->service_worker_object()) { + service_worker->service_worker_object()->Abort(); + } + } + } + + // Wait for web context registrations to be cleared. + web_context_registrations_cleared_.TimedWait(kShutdownWaitTimeoutSecs); + } +} + +void ServiceWorkerContext::StartRegister( + const base::Optional& maybe_scope_url, + const GURL& script_url_with_fragment, + std::unique_ptr promise_reference, + web::Context* client, const WorkerType& type, + const ServiceWorkerUpdateViaCache& update_via_cache) { + TRACE_EVENT2("cobalt::worker", "ServiceWorkerContext::StartRegister()", + "scope", maybe_scope_url.value_or(GURL()).spec(), "script", + script_url_with_fragment.spec()); + DCHECK_NE(message_loop(), base::MessageLoop::current()); + DCHECK_EQ(client->message_loop(), base::MessageLoop::current()); + // Algorithm for Start Register: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm + // 1. If scriptURL is failure, reject promise with a TypeError and abort these + // steps. + if (script_url_with_fragment.is_empty()) { + promise_reference->value().Reject(script::kTypeError); + return; + } + + // 2. Set scriptURL’s fragment to null. + url::Replacements replacements; + replacements.ClearRef(); + GURL script_url = script_url_with_fragment.ReplaceComponents(replacements); + DCHECK(!script_url.has_ref() || script_url.ref().empty()); + DCHECK(!script_url.is_empty()); + + // 3. If scriptURL’s scheme is not one of "http" and "https", reject promise + // with a TypeError and abort these steps. + if (!script_url.SchemeIsHTTPOrHTTPS()) { + promise_reference->value().Reject(script::kTypeError); + return; + } + + // 4. If any of the strings in scriptURL’s path contains either ASCII + // case-insensitive "%2f" or ASCII case-insensitive "%5c", reject promise + // with a TypeError and abort these steps. + if (PathContainsEscapedSlash(script_url)) { + promise_reference->value().Reject(script::kTypeError); + return; + } + + DCHECK(client); + web::WindowOrWorkerGlobalScope* window_or_worker_global_scope = + client->GetWindowOrWorkerGlobalScope(); + DCHECK(window_or_worker_global_scope); + web::CspDelegate* csp_delegate = + window_or_worker_global_scope->csp_delegate(); + DCHECK(csp_delegate); + if (!csp_delegate->CanLoad(web::CspDelegate::kWorker, script_url, + /* did_redirect*/ false)) { + promise_reference->value().Reject(new web::DOMException( + web::DOMException::kSecurityErr, + "Failed to register a ServiceWorker: The provided scriptURL ('" + + script_url.spec() + "') violates the Content Security Policy.")); + return; + } + + // 5. If scopeURL is null, set scopeURL to the result of parsing the string + // "./" with scriptURL. + GURL scope_url = maybe_scope_url.value_or(script_url.Resolve("./")); + + // 6. If scopeURL is failure, reject promise with a TypeError and abort these + // steps. + if (scope_url.is_empty()) { + promise_reference->value().Reject(script::kTypeError); + return; + } + + // 7. Set scopeURL’s fragment to null. + scope_url = scope_url.ReplaceComponents(replacements); + DCHECK(!scope_url.has_ref() || scope_url.ref().empty()); + DCHECK(!scope_url.is_empty()); + + // 8. If scopeURL’s scheme is not one of "http" and "https", reject promise + // with a TypeError and abort these steps. + if (!scope_url.SchemeIsHTTPOrHTTPS()) { + promise_reference->value().Reject(script::kTypeError); + return; + } + + // 9. If any of the strings in scopeURL’s path contains either ASCII + // case-insensitive "%2f" or ASCII case-insensitive "%5c", reject promise + // with a TypeError and abort these steps. + if (PathContainsEscapedSlash(scope_url)) { + promise_reference->value().Reject(script::kTypeError); + return; + } + + // 10. Let storage key be the result of running obtain a storage key given + // client. + url::Origin storage_key = client->environment_settings()->ObtainStorageKey(); + + // 11. Let job be the result of running Create Job with register, storage key, + // scopeURL, scriptURL, promise, and client. + std::unique_ptr job = jobs_->CreateJob( + ServiceWorkerJobs::kRegister, storage_key, scope_url, script_url, + ServiceWorkerJobs::JobPromiseType::Create(std::move(promise_reference)), + client); + + // 12. Set job’s worker type to workerType. + // Cobalt only supports 'classic' worker type. + + // 13. Set job’s update via cache mode to updateViaCache. + job->update_via_cache = update_via_cache; + + // 14. Set job’s referrer to referrer. + // This is the same value as set in CreateJob(). + + // 15. Invoke Schedule Job with job. + jobs_->ScheduleJob(std::move(job)); +} + +void ServiceWorkerContext::SoftUpdate( + ServiceWorkerRegistrationObject* registration, bool force_bypass_cache) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::SoftUpdate()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + DCHECK(registration); + // Algorithm for SoftUpdate: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#soft-update + // 1. Let newestWorker be the result of running Get Newest Worker algorithm + // passing registration as its argument. + ServiceWorkerObject* newest_worker = registration->GetNewestWorker(); + + // 2. If newestWorker is null, abort these steps. + if (newest_worker == nullptr) { + return; + } + + // 3. Let job be the result of running Create Job with update, registration’s + // storage key, registration’s scope url, newestWorker’s script url, null, and + // null. + std::unique_ptr job = jobs_->CreateJobWithoutPromise( + ServiceWorkerJobs::kUpdate, registration->storage_key(), + registration->scope_url(), newest_worker->script_url()); + + // 4. Set job’s worker type to newestWorker’s type. + // Cobalt only supports 'classic' worker type. + + // 5. Set job’s force bypass cache flag if forceBypassCache is true. + job->force_bypass_cache_flag = force_bypass_cache; + + // 6. Invoke Schedule Job with job. + message_loop()->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&ServiceWorkerJobs::ScheduleJob, + base::Unretained(jobs_.get()), std::move(job))); + DCHECK(!job.get()); +} + +void ServiceWorkerContext::EnsureServiceWorkerStarted( + const url::Origin& storage_key, const GURL& client_url, + base::WaitableEvent* done_event) { + if (message_loop() != base::MessageLoop::current()) { + message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce(&ServiceWorkerContext::EnsureServiceWorkerStarted, + base::Unretained(this), storage_key, client_url, + done_event)); + return; + } + base::ScopedClosureRunner signal_done(base::BindOnce( + [](base::WaitableEvent* done_event) { done_event->Signal(); }, + done_event)); + base::TimeTicks start = base::TimeTicks::Now(); + auto registration = + scope_to_registration_map_->GetRegistration(storage_key, client_url); + if (!registration) { + return; + } + auto service_worker_object = registration->active_worker(); + if (!service_worker_object || service_worker_object->is_running()) { + return; + } + service_worker_object->ObtainWebAgentAndWaitUntilDone(); +} + +std::string* ServiceWorkerContext::RunServiceWorker(ServiceWorkerObject* worker, + bool force_bypass_cache) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::RunServiceWorker()"); + + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + DCHECK(worker); + // Algorithm for "Run Service Worker" + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm + + // 1. Let unsafeCreationTime be the unsafe shared current time. + auto unsafe_creation_time = base::TimeTicks::Now(); + // 2. If serviceWorker is running, then return serviceWorker’s start status. + if (worker->is_running()) { + return worker->start_status(); + } + // 3. If serviceWorker’s state is "redundant", then return failure. + if (worker->state() == kServiceWorkerStateRedundant) { + return nullptr; + } + // 4. Assert: serviceWorker’s start status is null. + DCHECK(worker->start_status() == nullptr); + // 5. Let script be serviceWorker’s script resource. + // 6. Assert: script is not null. + DCHECK(worker->HasScriptResource()); + // 7. Let startFailed be false. + worker->store_start_failed(false); + // 8. Let agent be the result of obtaining a service worker agent, and run the + // following steps in that context: + // 9. Wait for serviceWorker to be running, or for startFailed to be true. + worker->ObtainWebAgentAndWaitUntilDone(); + // 10. If startFailed is true, then return failure. + if (worker->load_start_failed()) { + return nullptr; + } + // 11. Return serviceWorker’s start status. + return worker->start_status(); +} + +bool ServiceWorkerContext::WaitForAsynchronousExtensions( + const scoped_refptr& registration) { + // TODO(b/240164388): Investigate a better approach for combining waiting + // for the ExtendableEvent while also allowing use of algorithms that run + // on the same thread from the event handler. + base::TimeTicks wait_start_time = base::TimeTicks::Now(); + do { + if (registration->done_event()->TimedWait( + base::TimeDelta::FromMilliseconds(100))) + break; + base::MessageLoopCurrent::ScopedNestableTaskAllower allow; + base::RunLoop().RunUntilIdle(); + } while ((base::TimeTicks::Now() - wait_start_time) < + kWaitForAsynchronousExtensionsTimeout); + return registration->done_event()->IsSignaled(); +} + +bool ServiceWorkerContext::IsAnyClientUsingRegistration( + ServiceWorkerRegistrationObject* registration) { + bool any_client_is_using = false; + for (auto& context : web_context_registrations_) { + // When a service worker client is controlled by a service worker, it is + // said that the service worker client is using the service worker’s + // containing service worker registration. + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control + if (context->is_controlled_by(registration->active_worker())) { + any_client_is_using = true; + break; + } + } + return any_client_is_using; +} + +void ServiceWorkerContext::TryActivate( + ServiceWorkerRegistrationObject* registration) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::TryActivate()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Algorithm for Try Activate: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm + + // 1. If registration’s waiting worker is null, return. + if (!registration) return; + if (!registration->waiting_worker()) return; + + // 2. If registration’s active worker is not null and registration’s active + // worker's state is "activating", return. + if (registration->active_worker() && + (registration->active_worker()->state() == kServiceWorkerStateActivating)) + return; + + // 3. Invoke Activate with registration if either of the following is true: + + // - registration’s active worker is null. + bool invoke_activate = registration->active_worker() == nullptr; + + if (!invoke_activate) { + // - The result of running Service Worker Has No Pending Events with + // registration’s active worker is true... + if (ServiceWorkerHasNoPendingEvents(registration->active_worker())) { + // ... and no service worker client is using registration... + bool any_client_using = IsAnyClientUsingRegistration(registration); + invoke_activate = !any_client_using; + // ... or registration’s waiting worker's skip waiting flag is + // set. + if (!invoke_activate && registration->waiting_worker()->skip_waiting()) + invoke_activate = true; + } + } + + if (invoke_activate) Activate(registration); +} + +void ServiceWorkerContext::Activate( + ServiceWorkerRegistrationObject* registration) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::Activate()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Algorithm for Activate: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm + + // 1. If registration’s waiting worker is null, abort these steps. + if (registration->waiting_worker() == nullptr) return; + // 2. If registration’s active worker is not null, then: + if (registration->active_worker()) { + // 2.1. Terminate registration’s active worker. + TerminateServiceWorker(registration->active_worker()); + // 2.2. Run the Update Worker State algorithm passing registration’s active + // worker and "redundant" as the arguments. + UpdateWorkerState(registration->active_worker(), + kServiceWorkerStateRedundant); + } + // 3. Run the Update Registration State algorithm passing registration, + // "active" and registration’s waiting worker as the arguments. + UpdateRegistrationState(registration, kActive, + registration->waiting_worker()); + // 4. Run the Update Registration State algorithm passing registration, + // "waiting" and null as the arguments. + UpdateRegistrationState(registration, kWaiting, nullptr); + // 5. Run the Update Worker State algorithm passing registration’s active + // worker and "activating" as the arguments. + UpdateWorkerState(registration->active_worker(), + kServiceWorkerStateActivating); + // 6. Let matchedClients be a list of service worker clients whose creation + // URL matches registration’s storage key and registration’s scope url. + std::list matched_clients; + for (auto& context : web_context_registrations_) { + url::Origin context_storage_key = + url::Origin::Create(context->environment_settings()->creation_url()); + scoped_refptr matched_registration = + scope_to_registration_map_->MatchServiceWorkerRegistration( + context_storage_key, registration->scope_url()); + if (matched_registration == registration) { + matched_clients.push_back(context); + } + } + // 7. For each client of matchedClients, queue a task on client’s responsible + // event loop, using the DOM manipulation task source, to run the following + // substeps: + for (auto& client : matched_clients) { + // 7.1. Let readyPromise be client’s global object's + // ServiceWorkerContainer object’s ready + // promise. + // 7.2. If readyPromise is null, then continue. + // 7.3. If readyPromise is pending, resolve + // readyPromise with the the result of getting + // the service worker registration object that + // represents registration in readyPromise’s + // relevant settings object. + client->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce(&ServiceWorkerContainer::MaybeResolveReadyPromise, + base::Unretained(client->GetWindowOrWorkerGlobalScope() + ->navigator_base() + ->service_worker() + .get()), + base::Unretained(registration))); + } + // 8. For each client of matchedClients: + // 8.1. If client is a window client, unassociate client’s responsible + // document from its application cache, if it has one. + // 8.2. Else if client is a shared worker client, unassociate client’s + // global object from its application cache, if it has one. + // Cobalt doesn't implement 'application cache': + // https://www.w3.org/TR/2011/WD-html5-20110525/offline.html#applicationcache + // 9. For each service worker client client who is using registration: + // Note: The spec defines "control" and "use" of a service worker from the + // value of the active service worker property of the client environment, but + // that property is set here, so here we should not use that exact definition + // to determine if the client is using this registration. Instead, we use the + // Match Service Worker Registration algorithm to find the registration for a + // client and compare it with the registration being activated. + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-use + for (const auto& client : web_context_registrations_) { + scoped_refptr client_registration = + scope_to_registration_map_->MatchServiceWorkerRegistration( + client->environment_settings()->ObtainStorageKey(), + client->environment_settings()->creation_url()); + // When a service worker client is controlled by a service worker, it is + // said that the service worker client is using the service worker’s + // containing service worker registration. + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control + if (client_registration.get() == registration) { + // 9.1. Set client’s active worker to registration’s active worker. + client->set_active_service_worker(registration->active_worker()); + // 9.2. Invoke Notify Controller Change algorithm with client as the + // argument. + NotifyControllerChange(client); + } + } + // 10. Let activeWorker be registration’s active worker. + ServiceWorkerObject* active_worker = registration->active_worker(); + bool activated = true; + // 11. If the result of running the Should Skip Event algorithm with + // activeWorker and "activate" is false, then: + DCHECK(active_worker); + if (!active_worker->ShouldSkipEvent(base::Tokens::activate())) { + // 11.1. If the result of running the Run Service Worker algorithm with + // activeWorker is not failure, then: + auto* run_result = RunServiceWorker(active_worker); + if (run_result) { + // 11.1.1. Queue a task task on activeWorker’s event loop using the DOM + // manipulation task source to run the following steps: + DCHECK_EQ(active_worker->web_agent()->context(), + active_worker->worker_global_scope() + ->environment_settings() + ->context()); + DCHECK(registration->done_event()->IsSignaled()); + registration->done_event()->Reset(); + active_worker->web_agent() + ->context() + ->message_loop() + ->task_runner() + ->PostBlockingTask( + FROM_HERE, + base::Bind( + [](ServiceWorkerObject* active_worker, + base::WaitableEvent* done_event) { + auto done_callback = + base::BindOnce([](base::WaitableEvent* done_event, + bool) { done_event->Signal(); }, + done_event); + auto* settings = active_worker->web_agent() + ->context() + ->environment_settings(); + scoped_refptr event( + new ExtendableEvent(settings, base::Tokens::activate(), + std::move(done_callback))); + // 11.1.1.1. Let e be the result of creating an event with + // ExtendableEvent. + // 11.1.1.2. Initialize e’s type attribute to activate. + // 11.1.1.3. Dispatch e at activeWorker’s global object. + active_worker->worker_global_scope()->DispatchEvent(event); + // 11.1.1.4. WaitForAsynchronousExtensions: Wait, in + // parallel, until e is not active. + if (!event->IsActive()) { + // If the event handler doesn't use waitUntil(), it will + // already no longer be active, and there will never be a + // callback to signal the done event. + done_event->Signal(); + } + }, + base::Unretained(active_worker), registration->done_event())); + // 11.1.2. Wait for task to have executed or been discarded. + // This waiting is done inside PostBlockingTask above. + // 11.1.3. Wait for the step labeled WaitForAsynchronousExtensions to + // complete. + // TODO(b/240164388): Investigate a better approach for combining waiting + // for the ExtendableEvent while also allowing use of algorithms that run + // on the same thread from the event handler. + if (!WaitForAsynchronousExtensions(registration)) { + // Timeout + activated = false; + } + } else { + activated = false; + } + } + // 12. Run the Update Worker State algorithm passing registration’s active + // worker and "activated" as the arguments. + if (activated && registration->active_worker()) { + UpdateWorkerState(registration->active_worker(), + kServiceWorkerStateActivated); + + // Persist registration since the waiting_worker has been updated to nullptr + // and the active_worker has been updated to the previous waiting_worker. + scope_to_registration_map_->PersistRegistration(registration->storage_key(), + registration->scope_url()); + } +} + +void ServiceWorkerContext::NotifyControllerChange(web::Context* client) { + // Algorithm for Notify Controller Change: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm + // 1. Assert: client is not null. + DCHECK(client); + + // 2. If client is an environment settings object, queue a task to fire an + // event named controllerchange at the ServiceWorkerContainer object that + // client is associated with. + client->message_loop()->task_runner()->PostTask( + FROM_HERE, base::Bind( + [](web::Context* client) { + client->GetWindowOrWorkerGlobalScope() + ->navigator_base() + ->service_worker() + ->DispatchEvent(new web::Event( + base::Tokens::controllerchange())); + }, + client)); +} + +bool ServiceWorkerContext::ServiceWorkerHasNoPendingEvents( + ServiceWorkerObject* worker) { + // Algorithm for Service Worker Has No Pending Events + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events + // TODO(b/240174245): Implement this using the 'set of extended events'. + NOTIMPLEMENTED(); + + // 1. For each event of worker’s set of extended events: + // 1.1. If event is active, return false. + // 2. Return true. + return true; +} + +void ServiceWorkerContext::ClearRegistration( + ServiceWorkerRegistrationObject* registration) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::ClearRegistration()"); + // Algorithm for Clear Registration: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm + // 1. Run the following steps atomically. + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + + // 2. If registration’s installing worker is not null, then: + ServiceWorkerObject* installing_worker = registration->installing_worker(); + if (installing_worker) { + // 2.1. Terminate registration’s installing worker. + TerminateServiceWorker(installing_worker); + // 2.2. Run the Update Worker State algorithm passing registration’s + // installing worker and "redundant" as the arguments. + UpdateWorkerState(installing_worker, kServiceWorkerStateRedundant); + // 2.3. Run the Update Registration State algorithm passing registration, + // "installing" and null as the arguments. + UpdateRegistrationState(registration, kInstalling, nullptr); + } + + // 3. If registration’s waiting worker is not null, then: + ServiceWorkerObject* waiting_worker = registration->waiting_worker(); + if (waiting_worker) { + // 3.1. Terminate registration’s waiting worker. + TerminateServiceWorker(waiting_worker); + // 3.2. Run the Update Worker State algorithm passing registration’s + // waiting worker and "redundant" as the arguments. + UpdateWorkerState(waiting_worker, kServiceWorkerStateRedundant); + // 3.3. Run the Update Registration State algorithm passing registration, + // "waiting" and null as the arguments. + UpdateRegistrationState(registration, kWaiting, nullptr); + } + + // 4. If registration’s active worker is not null, then: + ServiceWorkerObject* active_worker = registration->active_worker(); + if (active_worker) { + // 4.1. Terminate registration’s active worker. + TerminateServiceWorker(active_worker); + // 4.2. Run the Update Worker State algorithm passing registration’s + // active worker and "redundant" as the arguments. + UpdateWorkerState(active_worker, kServiceWorkerStateRedundant); + // 4.3. Run the Update Registration State algorithm passing registration, + // "active" and null as the arguments. + UpdateRegistrationState(registration, kActive, nullptr); + } + + // Persist registration since the waiting_worker and active_worker have + // been updated to nullptr. This will remove any persisted registration + // if one exists. + scope_to_registration_map_->PersistRegistration(registration->storage_key(), + registration->scope_url()); +} + +void ServiceWorkerContext::TryClearRegistration( + ServiceWorkerRegistrationObject* registration) { + TRACE_EVENT0("cobalt::worker", + "ServiceWorkerContext::TryClearRegistration()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Algorithm for Try Clear Registration: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm + + // 1. Invoke Clear Registration with registration if no service worker client + // is using registration and all of the following conditions are true: + if (IsAnyClientUsingRegistration(registration)) return; + + // . registration’s installing worker is null or the result of running + // Service Worker Has No Pending Events with registration’s installing + // worker is true. + if (registration->installing_worker() && + !ServiceWorkerHasNoPendingEvents(registration->installing_worker())) + return; + + // . registration’s waiting worker is null or the result of running + // Service Worker Has No Pending Events with registration’s waiting + // worker is true. + if (registration->waiting_worker() && + !ServiceWorkerHasNoPendingEvents(registration->waiting_worker())) + return; + + // . registration’s active worker is null or the result of running + // ServiceWorker Has No Pending Events with registration’s active worker + // is true. + if (registration->active_worker() && + !ServiceWorkerHasNoPendingEvents(registration->active_worker())) + return; + + ClearRegistration(registration); +} + +void ServiceWorkerContext::UpdateRegistrationState( + ServiceWorkerRegistrationObject* registration, RegistrationState target, + const scoped_refptr& source) { + TRACE_EVENT2("cobalt::worker", + "ServiceWorkerContext::UpdateRegistrationState()", "target", + target, "source", source); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + DCHECK(registration); + // Algorithm for Update Registration State: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm + + // 1. Let registrationObjects be an array containing all the + // ServiceWorkerRegistration objects associated with registration. + // This is implemented with a call to LookupServiceWorkerRegistration for each + // registered web context. + + switch (target) { + // 2. If target is "installing", then: + case kInstalling: { + // 2.1. Set registration’s installing worker to source. + registration->set_installing_worker(source); + // 2.2. For each registrationObject in registrationObjects: + for (auto& context : web_context_registrations_) { + // 2.2.1. Queue a task to... + context->message_loop()->task_runner()->PostBlockingTask( + FROM_HERE, + base::Bind( + [](web::Context* context, + ServiceWorkerRegistrationObject* registration) { + // 2.2.1. ... set the installing attribute of + // registrationObject to null if registration’s + // installing worker is null, or the result of getting + // the service worker object that represents + // registration’s installing worker in + // registrationObject’s relevant settings object. + auto registration_object = + context->LookupServiceWorkerRegistration(registration); + if (registration_object) { + registration_object->set_installing( + context->GetServiceWorker( + registration->installing_worker())); + } + }, + context, base::Unretained(registration))); + } + break; + } + // 3. Else if target is "waiting", then: + case kWaiting: { + // 3.1. Set registration’s waiting worker to source. + registration->set_waiting_worker(source); + // 3.2. For each registrationObject in registrationObjects: + for (auto& context : web_context_registrations_) { + // 3.2.1. Queue a task to... + context->message_loop()->task_runner()->PostBlockingTask( + FROM_HERE, + base::Bind( + [](web::Context* context, + ServiceWorkerRegistrationObject* registration) { + // 3.2.1. ... set the waiting attribute of registrationObject + // to null if registration’s waiting worker is null, or + // the result of getting the service worker object that + // represents registration’s waiting worker in + // registrationObject’s relevant settings object. + auto registration_object = + context->LookupServiceWorkerRegistration(registration); + if (registration_object) { + registration_object->set_waiting(context->GetServiceWorker( + registration->waiting_worker())); + } + }, + context, base::Unretained(registration))); + } + break; + } + // 4. Else if target is "active", then: + case kActive: { + // 4.1. Set registration’s active worker to source. + registration->set_active_worker(source); + // 4.2. For each registrationObject in registrationObjects: + for (auto& context : web_context_registrations_) { + // 4.2.1. Queue a task to... + context->message_loop()->task_runner()->PostBlockingTask( + FROM_HERE, + base::Bind( + [](web::Context* context, + ServiceWorkerRegistrationObject* registration) { + // 4.2.1. ... set the active attribute of registrationObject + // to null if registration’s active worker is null, or + // the result of getting the service worker object that + // represents registration’s active worker in + // registrationObject’s relevant settings object. + auto registration_object = + context->LookupServiceWorkerRegistration(registration); + if (registration_object) { + registration_object->set_active(context->GetServiceWorker( + registration->active_worker())); + } + }, + context, base::Unretained(registration))); + } + break; + } + default: + NOTREACHED(); + } +} + +void ServiceWorkerContext::UpdateWorkerState(ServiceWorkerObject* worker, + ServiceWorkerState state) { + TRACE_EVENT1("cobalt::worker", "ServiceWorkerContext::UpdateWorkerState()", + "state", state); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + DCHECK(worker); + if (!worker) { + return; + } + // Algorithm for Update Worker State: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm + // 1. Assert: state is not "parsed". + DCHECK_NE(kServiceWorkerStateParsed, state); + // 2. Set worker's state to state. + worker->set_state(state); + auto worker_origin = loader::Origin(worker->script_url()); + // 3. Let settingsObjects be all environment settings objects whose origin is + // worker's script url's origin. + // 4. For each settingsObject of settingsObjects... + for (auto& context : web_context_registrations_) { + if (context->environment_settings()->GetOrigin() == worker_origin) { + // 4. ... queue a task on + // settingsObject's responsible event loop in the DOM manipulation task + // source to run the following steps: + context->message_loop()->task_runner()->PostBlockingTask( + FROM_HERE, base::Bind( + [](web::Context* context, ServiceWorkerObject* worker, + ServiceWorkerState state) { + DCHECK_EQ(context->message_loop(), + base::MessageLoop::current()); + // 4.1. Let objectMap be settingsObject's service + // worker object + // map. + // 4.2. If objectMap[worker] does not exist, then + // abort these + // steps. + // 4.3. Let workerObj be objectMap[worker]. + auto worker_obj = + context->LookupServiceWorker(worker); + if (worker_obj) { + // 4.4. Set workerObj's state to state. + worker_obj->set_state(state); + // 4.5. Fire an event named statechange at + // workerObj. + worker_obj->DispatchEvent( + new web::Event(base::Tokens::statechange())); + } + }, + context, base::Unretained(worker), state)); + } + } +} + +void ServiceWorkerContext::HandleServiceWorkerClientUnload( + web::Context* client) { + TRACE_EVENT0("cobalt::worker", + "ServiceWorkerContext::HandleServiceWorkerClientUnload()"); + // Algorithm for Handle Servicer Worker Client Unload: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm + DCHECK(client); + // 1. Run the following steps atomically. + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + + // 2. Let registration be the service worker registration used by client. + // 3. If registration is null, abort these steps. + ServiceWorkerObject* active_service_worker = client->active_service_worker(); + if (!active_service_worker) return; + ServiceWorkerRegistrationObject* registration = + active_service_worker->containing_service_worker_registration(); + if (!registration) return; + + // 4. If any other service worker client is using registration, abort these + // steps. + // Ensure the client is already removed from the registrations when this runs. + DCHECK(web_context_registrations_.end() == + web_context_registrations_.find(client)); + if (IsAnyClientUsingRegistration(registration)) return; + + // 5. If registration is unregistered, invoke Try Clear Registration with + // registration. + if (scope_to_registration_map_ && + scope_to_registration_map_->IsUnregistered(registration)) { + TryClearRegistration(registration); + } + + // 6. Invoke Try Activate with registration. + TryActivate(registration); +} + +void ServiceWorkerContext::TerminateServiceWorker(ServiceWorkerObject* worker) { + TRACE_EVENT0("cobalt::worker", + "ServiceWorkerContext::TerminateServiceWorker()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Algorithm for Terminate Service Worker: + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker + // 1. Run the following steps in parallel with serviceWorker’s main loop: + // This runs in the ServiceWorkerRegistry thread. + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + + // 1.1. Let serviceWorkerGlobalScope be serviceWorker’s global object. + WorkerGlobalScope* service_worker_global_scope = + worker->worker_global_scope(); + + // 1.2. Set serviceWorkerGlobalScope’s closing flag to true. + if (service_worker_global_scope != nullptr) + service_worker_global_scope->set_closing_flag(true); + + // 1.3. Remove all the items from serviceWorker’s set of extended events. + // TODO(b/240174245): Implement 'set of extended events'. + + // 1.4. If there are any tasks, whose task source is either the handle fetch + // task source or the handle functional event task source, queued in + // serviceWorkerGlobalScope’s event loop’s task queues, queue them to + // serviceWorker’s containing service worker registration’s corresponding + // task queues in the same order using their original task sources, and + // discard all the tasks (including tasks whose task source is neither + // the handle fetch task source nor the handle functional event task + // source) from serviceWorkerGlobalScope’s event loop’s task queues + // without processing them. + // TODO(b/234787641): Queue tasks to the registration. + + // Note: This step is not in the spec, but without this step the service + // worker object map will always keep an entry with a service worker instance + // for the terminated service worker, which besides leaking memory can lead to + // unexpected behavior when new service worker objects are created with the + // same key for the service worker object map (which in Cobalt's case + // happens when a new service worker object is constructed at the same + // memory address). + for (auto& context : web_context_registrations_) { + context->message_loop()->task_runner()->PostBlockingTask( + FROM_HERE, base::Bind( + [](web::Context* context, ServiceWorkerObject* worker) { + auto worker_obj = context->LookupServiceWorker(worker); + if (worker_obj) { + worker_obj->set_state(kServiceWorkerStateRedundant); + worker_obj->DispatchEvent( + new web::Event(base::Tokens::statechange())); + } + context->RemoveServiceWorker(worker); + }, + context, base::Unretained(worker))); + } + + // 1.5. Abort the script currently running in serviceWorker. + if (worker->is_running()) { + worker->Abort(); + } + + // 1.6. Set serviceWorker’s start status to null. + worker->set_start_status(nullptr); +} + +void ServiceWorkerContext::MaybeResolveReadyPromiseSubSteps( + web::Context* client) { + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Algorithm for Sub steps of ServiceWorkerContainer.ready(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-ready + + // 3.1. Let client by this's service worker client. + // 3.2. Let storage key be the result of running obtain a storage + // key given client. + url::Origin storage_key = client->environment_settings()->ObtainStorageKey(); + // 3.3. Let registration be the result of running Match Service + // Worker Registration given storage key and client’s + // creation URL. + // TODO(b/234659851): Investigate whether this should use the creation URL + // directly instead. + const GURL& base_url = client->environment_settings()->creation_url(); + GURL client_url = base_url.Resolve(""); + scoped_refptr registration = + scope_to_registration_map_->MatchServiceWorkerRegistration(storage_key, + client_url); + // 3.3. If registration is not null, and registration’s active + // worker is not null, queue a task on readyPromise’s + // relevant settings object's responsible event loop, using + // the DOM manipulation task source, to resolve readyPromise + // with the result of getting the service worker + // registration object that represents registration in + // readyPromise’s relevant settings object. + if (registration && registration->active_worker()) { + client->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce(&ServiceWorkerContainer::MaybeResolveReadyPromise, + base::Unretained(client->GetWindowOrWorkerGlobalScope() + ->navigator_base() + ->service_worker() + .get()), + registration)); + } +} + +void ServiceWorkerContext::GetRegistrationSubSteps( + const url::Origin& storage_key, const GURL& client_url, + web::Context* client, + std::unique_ptr + promise_reference) { + TRACE_EVENT0("cobalt::worker", + "ServiceWorkerContext::GetRegistrationSubSteps()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Algorithm for Sub steps of ServiceWorkerContainer.getRegistration(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration + + // 8.1. Let registration be the result of running Match Service Worker + // Registration algorithm with clientURL as its argument. + scoped_refptr registration = + scope_to_registration_map_->MatchServiceWorkerRegistration(storage_key, + client_url); + // 8.2. If registration is null, resolve promise with undefined and abort + // these steps. + // 8.3. Resolve promise with the result of getting the service worker + // registration object that represents registration in promise’s + // relevant settings object. + client->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](web::Context* client, + std::unique_ptr promise, + scoped_refptr registration) { + TRACE_EVENT0( + "cobalt::worker", + "ServiceWorkerContext::GetRegistrationSubSteps() Resolve"); + promise->value().Resolve( + client->GetServiceWorkerRegistration(registration)); + }, + client, std::move(promise_reference), registration)); +} + +void ServiceWorkerContext::GetRegistrationsSubSteps( + const url::Origin& storage_key, web::Context* client, + std::unique_ptr + promise_reference) { + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + std::vector> + registration_objects = + scope_to_registration_map_->GetRegistrations(storage_key); + client->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](web::Context* client, + std::unique_ptr + promise, + std::vector> + registration_objects) { + TRACE_EVENT0( + "cobalt::worker", + "ServiceWorkerContext::GetRegistrationSubSteps() Resolve"); + script::Sequence> registrations; + for (auto registration_object : registration_objects) { + registrations.push_back(scoped_refptr( + client->GetServiceWorkerRegistration(registration_object) + .get())); + } + promise->value().Resolve(std::move(registrations)); + }, + client, std::move(promise_reference), + std::move(registration_objects))); +} + +void ServiceWorkerContext::SkipWaitingSubSteps( + web::Context* worker_context, ServiceWorkerObject* service_worker, + std::unique_ptr promise_reference) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::SkipWaitingSubSteps()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Check if the client web context is still active. This may trigger if + // skipWaiting() was called and service worker installation fails. + if (!IsWebContextRegistered(worker_context)) { + promise_reference.release(); + return; + } + + // Algorithm for Sub steps of ServiceWorkerGlobalScope.skipWaiting(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting + + // 2.1. Set service worker's skip waiting flag. + service_worker->set_skip_waiting(); + + // 2.2. Invoke Try Activate with service worker's containing service worker + // registration. + TryActivate(service_worker->containing_service_worker_registration()); + + // 2.3. Resolve promise with undefined. + worker_context->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](std::unique_ptr promise) { + promise->value().Resolve(); + }, + std::move(promise_reference))); +} + +void ServiceWorkerContext::WaitUntilSubSteps( + ServiceWorkerRegistrationObject* registration) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::WaitUntilSubSteps()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Sub steps for WaitUntil. + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil + // 5.2.2. If registration is unregistered, invoke Try Clear Registration + // with registration. + if (scope_to_registration_map_->IsUnregistered(registration)) { + TryClearRegistration(registration); + } + // 5.2.3. If registration is not null, invoke Try Activate with + // registration. + if (registration) { + TryActivate(registration); + } +} + +void ServiceWorkerContext::ClientsGetSubSteps( + web::Context* worker_context, + ServiceWorkerObject* associated_service_worker, + std::unique_ptr promise_reference, + const std::string& id) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::ClientsGetSubSteps()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Check if the client web context is still active. This may trigger if + // Clients.get() was called and service worker installation fails. + if (!IsWebContextRegistered(worker_context)) { + promise_reference.release(); + return; + } + // Parallel sub steps (2) for algorithm for Clients.get(id): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get + // 2.1. For each service worker client client where the result of running + // obtain a storage key given client equals the associated service + // worker's containing service worker registration's storage key: + const url::Origin& storage_key = + associated_service_worker->containing_service_worker_registration() + ->storage_key(); + for (auto& client : web_context_registrations_) { + url::Origin client_storage_key = + client->environment_settings()->ObtainStorageKey(); + if (client_storage_key.IsSameOriginWith(storage_key)) { + // 2.1.1. If client’s id is not id, continue. + if (client->environment_settings()->id() != id) continue; + + // 2.1.2. Wait for either client’s execution ready flag to be set or for + // client’s discarded flag to be set. + // Web Contexts exist only in the web_context_registrations_ set when they + // are both execution ready and not discarded. + + // 2.1.3. If client’s execution ready flag is set, then invoke Resolve Get + // Client Promise with client and promise, and abort these steps. + ResolveGetClientPromise(client, worker_context, + std::move(promise_reference)); + return; + } + } + // 2.2. Resolve promise with undefined. + worker_context->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](std::unique_ptr + promise_reference) { + TRACE_EVENT0("cobalt::worker", + "ServiceWorkerContext::ClientsGetSubSteps() Resolve"); + promise_reference->value().Resolve(scoped_refptr()); + }, + std::move(promise_reference))); +} + +void ServiceWorkerContext::ClientsMatchAllSubSteps( + web::Context* worker_context, + ServiceWorkerObject* associated_service_worker, + std::unique_ptr + promise_reference, + bool include_uncontrolled, ClientType type) { + TRACE_EVENT0("cobalt::worker", + "ServiceWorkerContext::ClientsMatchAllSubSteps()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + // Check if the worker web context is still active. This may trigger if + // Clients.matchAll() was called and service worker installation fails. + if (!IsWebContextRegistered(worker_context)) { + promise_reference.release(); + return; + } + + // Parallel sub steps (2) for algorithm for Clients.matchAll(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall + // 2.1. Let targetClients be a new list. + std::list target_clients; + + // 2.2. For each service worker client client where the result of running + // obtain a storage key given client equals the associated service + // worker's containing service worker registration's storage key: + const url::Origin& storage_key = + associated_service_worker->containing_service_worker_registration() + ->storage_key(); + for (auto& client : web_context_registrations_) { + url::Origin client_storage_key = + client->environment_settings()->ObtainStorageKey(); + if (client_storage_key.IsSameOriginWith(storage_key)) { + // 2.2.1. If client’s execution ready flag is unset or client’s discarded + // flag is set, continue. + // Web Contexts exist only in the web_context_registrations_ set when they + // are both execution ready and not discarded. + + // 2.2.2. If client is not a secure context, continue. + // In production, Cobalt requires https, therefore all workers and their + // owners are secure contexts. + + // 2.2.3. If options["includeUncontrolled"] is false, and if client’s + // active service worker is not the associated service worker, + // continue. + if (!include_uncontrolled && + (client->active_service_worker() != associated_service_worker)) { + continue; + } + + // 2.2.4. Add client to targetClients. + target_clients.push_back(client); + } + } + + // 2.3. Let matchedWindowData be a new list. + std::unique_ptr> matched_window_data( + new std::vector); + + // 2.4. Let matchedClients be a new list. + std::unique_ptr> matched_clients( + new std::vector); + + // 2.5. For each service worker client client in targetClients: + for (auto* client : target_clients) { + auto* global_scope = client->GetWindowOrWorkerGlobalScope(); + + if ((type == kClientTypeWindow || type == kClientTypeAll) && + (global_scope->IsWindow())) { + // 2.5.1. If options["type"] is "window" or "all", and client is not an + // environment settings object or is a window client, then: + + // 2.5.1.1. Let windowData be [ "client" -> client, "ancestorOriginsList" + // -> a new list ]. + WindowData window_data(client->environment_settings()); + + // 2.5.1.2. Let browsingContext be null. + + // 2.5.1.3. Let isClientEnumerable be true. + // For Cobalt, isClientEnumerable is always true because the clauses that + // would set it to false in 2.5.1.6. do not apply to Cobalt. + + // 2.5.1.4. If client is an environment settings object, set + // browsingContext to client’s global object's browsing context. + // 2.5.1.5. Else, set browsingContext to client’s target browsing context. + web::Context* browsing_context = client; + + // 2.5.1.6. Queue a task task to run the following substeps on + // browsingContext’s event loop using the user interaction task + // source: + // Note: The task below does not currently perform any actual + // functionality. It is included however to help future implementation for + // fetching values for WindowClient properties, with similar logic + // existing in ResolveGetClientPromise. + browsing_context->message_loop()->task_runner()->PostBlockingTask( + FROM_HERE, base::Bind( + [](WindowData* window_data) { + // 2.5.1.6.1. If browsingContext has been discarded, + // then set isClientEnumerable to false + // and abort these steps. + // 2.5.1.6.2. If client is a window client and + // client’s responsible document is not + // browsingContext’s active document, then + // set isClientEnumerable to false and + // abort these steps. + // In Cobalt, the document of a window browsing + // context doesn't change: When a new document is + // created, a new browsing context is created with + // it. + + // 2.5.1.6.3. Set windowData["frameType"] to the + // result of running Get Frame Type with + // browsingContext. + // Cobalt does not support nested or auxiliary + // browsing contexts. + // 2.5.1.6.4. Set windowData["visibilityState"] to + // browsingContext’s active document's + // visibilityState attribute value. + // 2.5.1.6.5. Set windowData["focusState"] to the + // result of running the has focus steps + // with browsingContext’s active document + // as the argument. + + // 2.5.1.6.6. If client is a window client, then set + // windowData["ancestorOriginsList"] to + // browsingContext’s active document's + // relevant global object's Location + // object’s ancestor origins list's + // associated list. + // Cobalt does not implement + // Location.ancestorOrigins. + }, + &window_data)); + + // 2.5.1.7. Wait for task to have executed. + // The task above is posted as a blocking task. + + // 2.5.1.8. If isClientEnumerable is true, then: + + // 2.5.1.8.1. Add windowData to matchedWindowData. + matched_window_data->emplace_back(window_data); + + // 2.5.2. Else if options["type"] is "worker" or "all" and client is a + // dedicated worker client, or options["type"] is "sharedworker" or + // "all" and client is a shared worker client, then: + } else if (((type == kClientTypeWorker || type == kClientTypeAll) && + global_scope->IsDedicatedWorker())) { + // Note: Cobalt does not support shared workers. + // 2.5.2.1. Add client to matchedClients. + matched_clients->emplace_back(client); + } + } + + // 2.6. Queue a task to run the following steps on promise’s relevant + // settings object's responsible event loop using the DOM manipulation + // task source: + worker_context->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](std::unique_ptr + promise_reference, + std::unique_ptr> matched_window_data, + std::unique_ptr> matched_clients) { + TRACE_EVENT0("cobalt::worker", + "ServiceWorkerContext::ClientsMatchAllSubSteps() " + "Resolve Promise"); + // 2.6.1. Let clientObjects be a new list. + script::Sequence> client_objects; + + // 2.6.2. For each windowData in matchedWindowData: + for (auto& window_data : *matched_window_data) { + // 2.6.2.1. Let WindowClient be the result of running + // Create Window Client algorithm with + // windowData["client"], + // windowData["frameType"], + // windowData["visibilityState"], + // windowData["focusState"], and + // windowData["ancestorOriginsList"] as the + // arguments. + // TODO(b/235838698): Implement WindowClient methods. + scoped_refptr window_client = + WindowClient::Create(window_data); + + // 2.6.2.2. Append WindowClient to clientObjects. + client_objects.push_back(window_client); + } + + // 2.6.3. For each client in matchedClients: + for (auto& client : *matched_clients) { + // 2.6.3.1. Let clientObject be the result of running + // Create Client algorithm with client as the + // argument. + scoped_refptr client_object = + Client::Create(client->environment_settings()); + + // 2.6.3.2. Append clientObject to clientObjects. + client_objects.push_back(client_object); + } + // 2.6.4. Sort clientObjects such that: + // . WindowClient objects whose browsing context has been + // focused are placed first, sorted in the most recently + // focused order. + // . WindowClient objects whose browsing context has never + // been focused are placed next, sorted in their service + // worker client's creation order. + // . Client objects whose associated service worker client is + // a worker client are placed next, sorted in their service + // worker client's creation order. + // TODO(b/235876598): Implement sorting of clientObjects. + + // 2.6.5. Resolve promise with a new frozen array of clientObjects + // in promise’s relevant Realm. + promise_reference->value().Resolve(client_objects); + }, + std::move(promise_reference), std::move(matched_window_data), + std::move(matched_clients))); +} + +void ServiceWorkerContext::ClaimSubSteps( + web::Context* worker_context, + ServiceWorkerObject* associated_service_worker, + std::unique_ptr promise_reference) { + TRACE_EVENT0("cobalt::worker", "ServiceWorkerContext::ClaimSubSteps()"); + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + + // Check if the client web context is still active. This may trigger if + // Clients.claim() was called and service worker installation fails. + if (!IsWebContextRegistered(worker_context)) { + promise_reference.release(); + return; + } + + // Parallel sub steps (3) for algorithm for Clients.claim(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-clients-claim + std::list target_clients; + + // 3.1. For each service worker client client where the result of running + // obtain a storage key given client equals the service worker's + // containing service worker registration's storage key: + const url::Origin& storage_key = + associated_service_worker->containing_service_worker_registration() + ->storage_key(); + for (auto& client : web_context_registrations_) { + // Don't claim to be our own service worker. + if (client == worker_context) continue; + url::Origin client_storage_key = + client->environment_settings()->ObtainStorageKey(); + if (client_storage_key.IsSameOriginWith(storage_key)) { + // 3.1.1. If client’s execution ready flag is unset or client’s discarded + // flag is set, continue. + // Web Contexts exist only in the web_context_registrations_ set when they + // are both execution ready and not discarded. + + // 3.1.2. If client is not a secure context, continue. + // In production, Cobalt requires https, therefore all clients are secure + // contexts. + + // 3.1.3. Let storage key be the result of running obtain a storage key + // given client. + // 3.1.4. Let registration be the result of running Match Service Worker + // Registration given storage key and client’s creation URL. + // TODO(b/234659851): Investigate whether this should use the creation + // URL directly instead. + const GURL& base_url = client->environment_settings()->creation_url(); + GURL client_url = base_url.Resolve(""); + scoped_refptr registration = + scope_to_registration_map_->MatchServiceWorkerRegistration( + client_storage_key, client_url); + + // 3.1.5. If registration is not the service worker's containing service + // worker registration, continue. + if (registration != + associated_service_worker->containing_service_worker_registration()) { + continue; + } + + // 3.1.6. If client’s active service worker is not the service worker, + // then: + if (client->active_service_worker() != associated_service_worker) { + // 3.1.6.1. Invoke Handle Service Worker Client Unload with client as + // the argument. + HandleServiceWorkerClientUnload(client); + + // 3.1.6.2. Set client’s active service worker to service worker. + client->set_active_service_worker(associated_service_worker); + + // 3.1.6.3. Invoke Notify Controller Change algorithm with client as the + // argument. + NotifyControllerChange(client); + } + } + } + // 3.2. Resolve promise with undefined. + worker_context->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](std::unique_ptr promise) { + promise->value().Resolve(); + }, + std::move(promise_reference))); +} + +void ServiceWorkerContext::ServiceWorkerPostMessageSubSteps( + ServiceWorkerObject* service_worker, web::Context* incumbent_client, + std::unique_ptr structured_clone) { + // Parallel sub steps (6) for algorithm for ServiceWorker.postMessage(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage-options + // 3. Let incumbentGlobal be incumbentSettings’s global object. + // Note: The 'incumbent' is the sender of the message. + // 6.1 If the result of running the Run Service Worker algorithm with + // serviceWorker is failure, then return. + auto* run_result = RunServiceWorker(service_worker); + if (!run_result) return; + if (!structured_clone || structured_clone->failed()) return; + + // 6.2 Queue a task on the DOM manipulation task source to run the following + // steps: + incumbent_client->message_loop()->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](ServiceWorkerObject* service_worker, + web::Context* incumbent_client, + std::unique_ptr structured_clone) { + web::EventTarget* event_target = + service_worker->worker_global_scope(); + if (!event_target) return; + + web::WindowOrWorkerGlobalScope* incumbent_global = + incumbent_client->GetWindowOrWorkerGlobalScope(); + DCHECK_EQ(incumbent_client->environment_settings(), + incumbent_global->environment_settings()); + base::TypeId incumbent_type = incumbent_global->GetWrappableType(); + ServiceWorkerObject* incumbent_worker = + incumbent_global->IsServiceWorker() + ? incumbent_global->AsServiceWorker() + ->service_worker_object() + : nullptr; + base::MessageLoop* message_loop = + event_target->environment_settings()->context()->message_loop(); + if (!message_loop) { + return; + } + message_loop->task_runner()->PostTask( + FROM_HERE, + base::BindOnce( + [](const base::TypeId& incumbent_type, + ServiceWorkerObject* incumbent_worker, + web::Context* incumbent_client, + web::EventTarget* event_target, + std::unique_ptr + structured_clone) { + ExtendableMessageEventInit init_dict; + if (incumbent_type == + base::GetTypeId()) { + // 6.2.1. Let source be determined by switching on the + // type of incumbentGlobal: + // . ServiceWorkerGlobalScope + // The result of getting the service worker + // object that represents incumbentGlobal’s + // service worker in the relevant settings + // object of serviceWorker’s global object. + init_dict.set_source(ExtendableMessageEvent::SourceType( + event_target->environment_settings() + ->context() + ->GetServiceWorker(incumbent_worker))); + } else if (incumbent_type == + base::GetTypeId()) { + // . Window + // a new WindowClient object that represents + // incumbentGlobal’s relevant settings object. + init_dict.set_source(ExtendableMessageEvent::SourceType( + WindowClient::Create(WindowData( + incumbent_client->environment_settings())))); + } else { + // . Otherwise + // a new Client object that represents + // incumbentGlobal’s associated worker + init_dict.set_source( + ExtendableMessageEvent::SourceType(Client::Create( + incumbent_client->environment_settings()))); + } + + event_target->DispatchEvent( + new worker::ExtendableMessageEvent( + event_target->environment_settings(), + base::Tokens::message(), init_dict, + std::move(structured_clone))); + }, + incumbent_type, base::Unretained(incumbent_worker), + // Note: These should probably be weak pointers for when + // the message sender disappears before the recipient + // processes the event, but since base::WeakPtr + // dereferencing isn't thread-safe, that can't actually be + // used here. + base::Unretained(incumbent_client), + base::Unretained(event_target), + std::move(structured_clone))); + }, + base::Unretained(service_worker), base::Unretained(incumbent_client), + std::move(structured_clone))); +} + +void ServiceWorkerContext::RegisterWebContext(web::Context* context) { + DCHECK_NE(nullptr, context); + web_context_registrations_cleared_.Reset(); + if (base::MessageLoop::current() != message_loop()) { + DCHECK(message_loop()); + message_loop()->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&ServiceWorkerContext::RegisterWebContext, + base::Unretained(this), context)); + return; + } + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + DCHECK_EQ(0, web_context_registrations_.count(context)); + web_context_registrations_.insert(context); +} + +void ServiceWorkerContext::SetActiveWorker(web::EnvironmentSettings* client) { + if (!client) return; + if (base::MessageLoop::current() != message_loop()) { + DCHECK(message_loop()); + message_loop()->task_runner()->PostTask( + FROM_HERE, base::Bind(&ServiceWorkerContext::SetActiveWorker, + base::Unretained(this), client)); + return; + } + DCHECK(scope_to_registration_map_); + scoped_refptr client_registration = + scope_to_registration_map_->MatchServiceWorkerRegistration( + client->ObtainStorageKey(), client->creation_url()); + if (client_registration.get() && client_registration->active_worker()) { + client->context()->set_active_service_worker( + client_registration->active_worker()); + } else { + client->context()->set_active_service_worker(nullptr); + } +} + +void ServiceWorkerContext::UnregisterWebContext(web::Context* context) { + DCHECK_NE(nullptr, context); + if (base::MessageLoop::current() != message_loop()) { + // Block to ensure that the context is unregistered before it is destroyed. + DCHECK(message_loop()); + message_loop()->task_runner()->PostBlockingTask( + FROM_HERE, base::Bind(&ServiceWorkerContext::UnregisterWebContext, + base::Unretained(this), context)); + return; + } + DCHECK_EQ(message_loop(), base::MessageLoop::current()); + DCHECK_EQ(1, web_context_registrations_.count(context)); + web_context_registrations_.erase(context); + HandleServiceWorkerClientUnload(context); + PrepareForClientShutdown(context); + if (web_context_registrations_.empty()) { + web_context_registrations_cleared_.Signal(); + } +} + +void ServiceWorkerContext::PrepareForClientShutdown(web::Context* client) { + DCHECK(client); + if (!client) return; + DCHECK(base::MessageLoop::current() == message_loop()); + // Note: This could be rewritten to use the decomposition declaration + // 'const auto& [scope, queue]' after switching to C++17. + jobs_->PrepareForClientShutdown(client); +} + +} // namespace worker +} // namespace cobalt diff --git a/cobalt/worker/service_worker_context.h b/cobalt/worker/service_worker_context.h new file mode 100644 index 000000000000..1446fe8d644c --- /dev/null +++ b/cobalt/worker/service_worker_context.h @@ -0,0 +1,221 @@ +// Copyright 2023 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. + +#ifndef COBALT_WORKER_SERVICE_WORKER_CONTEXT_H_ +#define COBALT_WORKER_SERVICE_WORKER_CONTEXT_H_ + +#include +#include +#include + +#include "base/memory/scoped_refptr.h" +#include "base/message_loop/message_loop.h" +#include "base/optional.h" +#include "base/synchronization/waitable_event.h" +#include "cobalt/network/network_module.h" +#include "cobalt/script/promise.h" +#include "cobalt/script/script_value.h" +#include "cobalt/script/script_value_factory.h" +#include "cobalt/web/context.h" +#include "cobalt/web/web_settings.h" +#include "cobalt/worker/client_query_options.h" +#include "cobalt/worker/service_worker_object.h" +#include "cobalt/worker/service_worker_registration_map.h" +#include "cobalt/worker/service_worker_registration_object.h" +#include "cobalt/worker/worker_type.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace cobalt { +namespace worker { + +class ServiceWorkerJobs; + +// Algorithms for Service Workers. +// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#algorithms +class ServiceWorkerContext { + public: + enum RegistrationState { kInstalling, kWaiting, kActive }; + + ServiceWorkerContext(web::WebSettings* web_settings, + network::NetworkModule* network_module, + web::UserAgentPlatformInfo* platform_info, + base::MessageLoop* message_loop, const GURL& url); + ~ServiceWorkerContext(); + + base::MessageLoop* message_loop() { return message_loop_; } + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm + void StartRegister(const base::Optional& scope_url, + const GURL& script_url, + std::unique_ptr + promise_reference, + web::Context* client, const WorkerType& type, + const ServiceWorkerUpdateViaCache& update_via_cache); + + void MaybeResolveReadyPromiseSubSteps(web::Context* client); + + // Sub steps (8) of ServiceWorkerContainer.getRegistration(). + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#navigator-service-worker-getRegistration + void GetRegistrationSubSteps( + const url::Origin& storage_key, const GURL& client_url, + web::Context* client, + std::unique_ptr + promise_reference); + + void GetRegistrationsSubSteps( + const url::Origin& storage_key, web::Context* client, + std::unique_ptr + promise_reference); + + // Sub steps (2) of ServiceWorkerGlobalScope.skipWaiting(). + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-serviceworkerglobalscope-skipwaiting + void SkipWaitingSubSteps( + web::Context* worker_context, ServiceWorkerObject* service_worker, + std::unique_ptr promise_reference); + + // Sub steps for ExtendableEvent.WaitUntil(). + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-extendableevent-waituntil + void WaitUntilSubSteps(ServiceWorkerRegistrationObject* registration); + + // Parallel sub steps (2) for algorithm for Clients.get(id): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-get + void ClientsGetSubSteps( + web::Context* worker_context, + ServiceWorkerObject* associated_service_worker, + std::unique_ptr + promise_reference, + const std::string& id); + + // Parallel sub steps (2) for algorithm for Clients.matchAll(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clients-matchall + void ClientsMatchAllSubSteps( + web::Context* worker_context, + ServiceWorkerObject* associated_service_worker, + std::unique_ptr + promise_reference, + bool include_uncontrolled, ClientType type); + + // Parallel sub steps (3) for algorithm for Clients.claim(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dom-clients-claim + void ClaimSubSteps( + web::Context* worker_context, + ServiceWorkerObject* associated_service_worker, + std::unique_ptr promise_reference); + + // Parallel sub steps (6) for algorithm for ServiceWorker.postMessage(): + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-postmessage-options + void ServiceWorkerPostMessageSubSteps( + ServiceWorkerObject* service_worker, web::Context* incumbent_client, + std::unique_ptr structured_clone); + + // Registration of web contexts that may have service workers. + void RegisterWebContext(web::Context* context); + void UnregisterWebContext(web::Context* context); + bool IsWebContextRegistered(web::Context* context) { + DCHECK(base::MessageLoop::current() == message_loop()); + return web_context_registrations_.end() != + web_context_registrations_.find(context); + } + + // Ensure no references are kept to JS objects for a client that is about to + // be shutdown. + void PrepareForClientShutdown(web::Context* client); + + // Set the active worker for a client if there is a matching service worker. + void SetActiveWorker(web::EnvironmentSettings* client); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm + void Activate(ServiceWorkerRegistrationObject* registration); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm + void ClearRegistration(ServiceWorkerRegistrationObject* registration); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#soft-update + void SoftUpdate(ServiceWorkerRegistrationObject* registration, + bool force_bypass_cache); + + void EnsureServiceWorkerStarted(const url::Origin& storage_key, + const GURL& client_url, + base::WaitableEvent* done_event); + + ServiceWorkerJobs* jobs() { return jobs_.get(); } + ServiceWorkerRegistrationMap* registration_map() { + return scope_to_registration_map_.get(); + } + const std::set& web_context_registrations() const { + return web_context_registrations_; + } + + private: + friend class ServiceWorkerJobs; + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm + // The return value is a 'Completion or failure'. + // A failure is signaled by returning nullptr. Otherwise, the returned string + // points to the value of the Completion returned by the script runner + // abstraction. + std::string* RunServiceWorker(ServiceWorkerObject* worker, + bool force_bypass_cache = false); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm + void TryActivate(ServiceWorkerRegistrationObject* registration); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events + bool ServiceWorkerHasNoPendingEvents(ServiceWorkerObject* worker); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm + void UpdateRegistrationState( + ServiceWorkerRegistrationObject* registration, RegistrationState target, + const scoped_refptr& source); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm + void UpdateWorkerState(ServiceWorkerObject* worker, ServiceWorkerState state); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm + void HandleServiceWorkerClientUnload(web::Context* client); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker + void TerminateServiceWorker(ServiceWorkerObject* worker); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm + void NotifyControllerChange(web::Context* client); + + // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm + void TryClearRegistration(ServiceWorkerRegistrationObject* registration); + + bool IsAnyClientUsingRegistration( + ServiceWorkerRegistrationObject* registration); + + // Returns false when the timeout is reached. + bool WaitForAsynchronousExtensions( + const scoped_refptr& registration); + + base::MessageLoop* message_loop_; + + std::unique_ptr scope_to_registration_map_; + + std::unique_ptr jobs_; + + std::set web_context_registrations_; + + base::WaitableEvent web_context_registrations_cleared_ = { + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED}; +}; + +} // namespace worker +} // namespace cobalt + +#endif // COBALT_WORKER_SERVICE_WORKER_CONTEXT_H_ diff --git a/cobalt/worker/service_worker_global_scope.cc b/cobalt/worker/service_worker_global_scope.cc index 06b7dae27eae..495046fbd8db 100644 --- a/cobalt/worker/service_worker_global_scope.cc +++ b/cobalt/worker/service_worker_global_scope.cc @@ -29,7 +29,7 @@ #include "cobalt/worker/clients.h" #include "cobalt/worker/fetch_event.h" #include "cobalt/worker/fetch_event_init.h" -#include "cobalt/worker/service_worker_jobs.h" +#include "cobalt/worker/service_worker_context.h" #include "cobalt/worker/worker_settings.h" namespace cobalt { @@ -180,12 +180,12 @@ script::HandlePromiseVoid ServiceWorkerGlobalScope::SkipWaiting() { new script::ValuePromiseVoid::Reference(this, promise)); // 2. Run the following substeps in parallel: - worker::ServiceWorkerJobs* jobs = - environment_settings()->context()->service_worker_jobs(); - jobs->message_loop()->task_runner()->PostTask( + ServiceWorkerContext* worker_context = + environment_settings()->context()->service_worker_context(); + worker_context->message_loop()->task_runner()->PostTask( FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::SkipWaitingSubSteps, - base::Unretained(jobs), + base::BindOnce(&ServiceWorkerContext::SkipWaitingSubSteps, + base::Unretained(worker_context), base::Unretained(environment_settings()->context()), base::Unretained(service_worker_object_.get()), std::move(promise_reference))); @@ -226,13 +226,13 @@ void ServiceWorkerGlobalScope::StartFetch( auto* registration = service_worker_object_->containing_service_worker_registration(); if (registration && (main_resource || registration->stale())) { - worker::ServiceWorkerJobs* jobs = - environment_settings()->context()->service_worker_jobs(); - jobs->message_loop()->task_runner()->PostTask( - FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::SoftUpdate, base::Unretained(jobs), - base::Unretained(registration), - /*force_bypass_cache=*/false)); + ServiceWorkerContext* worker_context = + environment_settings()->context()->service_worker_context(); + worker_context->message_loop()->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&ServiceWorkerContext::SoftUpdate, + base::Unretained(worker_context), + base::Unretained(registration), + /*force_bypass_cache=*/false)); } // TODO: handle the following steps in diff --git a/cobalt/worker/service_worker_jobs.cc b/cobalt/worker/service_worker_jobs.cc index 25b2a58198c1..1b22096bdaf8 100644 --- a/cobalt/worker/service_worker_jobs.cc +++ b/cobalt/worker/service_worker_jobs.cc @@ -14,71 +14,22 @@ #include "cobalt/worker/service_worker_jobs.h" -#include -#include -#include -#include -#include -#include -#include - #include "base/bind.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_current.h" -#include "base/single_thread_task_runner.h" -#include "base/strings/string_util.h" #include "base/strings/stringprintf.h" -#include "base/synchronization/lock.h" -#include "base/task_runner.h" -#include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "cobalt/base/tokens.h" -#include "cobalt/base/type_id.h" -#include "cobalt/loader/script_loader_factory.h" -#include "cobalt/network/network_module.h" -#include "cobalt/script/promise.h" -#include "cobalt/script/script_exception.h" -#include "cobalt/script/script_value.h" -#include "cobalt/web/context.h" -#include "cobalt/web/dom_exception.h" #include "cobalt/web/environment_settings.h" -#include "cobalt/web/event.h" -#include "cobalt/web/window_or_worker_global_scope.h" -#include "cobalt/worker/client.h" -#include "cobalt/worker/client_query_options.h" -#include "cobalt/worker/client_type.h" #include "cobalt/worker/extendable_event.h" -#include "cobalt/worker/extendable_message_event.h" -#include "cobalt/worker/frame_type.h" -#include "cobalt/worker/service_worker.h" -#include "cobalt/worker/service_worker_consts.h" -#include "cobalt/worker/service_worker_container.h" -#include "cobalt/worker/service_worker_global_scope.h" -#include "cobalt/worker/service_worker_registration.h" -#include "cobalt/worker/service_worker_registration_object.h" -#include "cobalt/worker/service_worker_update_via_cache.h" -#include "cobalt/worker/window_client.h" -#include "cobalt/worker/worker_type.h" #include "net/base/mime_util.h" #include "net/base/url_util.h" -#include "starboard/common/atomic.h" -#include "url/gurl.h" -#include "url/origin.h" - namespace cobalt { namespace worker { namespace { -const base::TimeDelta kWaitForAsynchronousExtensionsTimeout = - base::TimeDelta::FromSeconds(3); - -const base::TimeDelta kShutdownWaitTimeoutSecs = - base::TimeDelta::FromSeconds(5); - bool PathContainsEscapedSlash(const GURL& url) { const std::string path = url.path(); return (path.find("%2f") != std::string::npos || @@ -134,164 +85,22 @@ bool IsOriginPotentiallyTrustworthy(const GURL& url) { bool PermitAnyNonRedirectedURL(const GURL&, bool did_redirect) { return !did_redirect; } + } // namespace -ServiceWorkerJobs::ServiceWorkerJobs(web::WebSettings* web_settings, - network::NetworkModule* network_module, - web::UserAgentPlatformInfo* platform_info, - base::MessageLoop* message_loop, - const GURL& url) - : message_loop_(message_loop) { +ServiceWorkerJobs::ServiceWorkerJobs( + ServiceWorkerContext* service_worker_context, + network::NetworkModule* network_module, base::MessageLoop* message_loop) + : service_worker_context_(service_worker_context), + message_loop_(message_loop) { DCHECK_EQ(message_loop_, base::MessageLoop::current()); fetcher_factory_.reset(new loader::FetcherFactory(network_module)); - DCHECK(fetcher_factory_); script_loader_factory_.reset(new loader::ScriptLoaderFactory( "ServiceWorkerJobs", fetcher_factory_.get())); - DCHECK(script_loader_factory_); - - ServiceWorkerPersistentSettings::Options options(web_settings, network_module, - platform_info, this, url); - scope_to_registration_map_.reset(new ServiceWorkerRegistrationMap(options)); - DCHECK(scope_to_registration_map_); } -ServiceWorkerJobs::~ServiceWorkerJobs() { - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - scope_to_registration_map_->HandleUserAgentShutdown(this); - scope_to_registration_map_->AbortAllActive(); - scope_to_registration_map_.reset(); - if (!web_context_registrations_.empty()) { - // Abort any Service Workers that remain. - for (auto& context : web_context_registrations_) { - DCHECK(context); - if (context->GetWindowOrWorkerGlobalScope()->IsServiceWorker()) { - ServiceWorkerGlobalScope* service_worker = - context->GetWindowOrWorkerGlobalScope()->AsServiceWorker(); - if (service_worker && service_worker->service_worker_object()) { - service_worker->service_worker_object()->Abort(); - } - } - } - - // Wait for web context registrations to be cleared. - web_context_registrations_cleared_.TimedWait(kShutdownWaitTimeoutSecs); - } -} - -void ServiceWorkerJobs::StartRegister( - const base::Optional& maybe_scope_url, - const GURL& script_url_with_fragment, - std::unique_ptr promise_reference, - web::Context* client, const WorkerType& type, - const ServiceWorkerUpdateViaCache& update_via_cache) { - TRACE_EVENT2("cobalt::worker", "ServiceWorkerJobs::StartRegister()", "scope", - maybe_scope_url.value_or(GURL()).spec(), "script", - script_url_with_fragment.spec()); - DCHECK_NE(message_loop(), base::MessageLoop::current()); - DCHECK_EQ(client->message_loop(), base::MessageLoop::current()); - // Algorithm for Start Register: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm - // 1. If scriptURL is failure, reject promise with a TypeError and abort these - // steps. - if (script_url_with_fragment.is_empty()) { - promise_reference->value().Reject(script::kTypeError); - return; - } - - // 2. Set scriptURL’s fragment to null. - url::Replacements replacements; - replacements.ClearRef(); - GURL script_url = script_url_with_fragment.ReplaceComponents(replacements); - DCHECK(!script_url.has_ref() || script_url.ref().empty()); - DCHECK(!script_url.is_empty()); - - // 3. If scriptURL’s scheme is not one of "http" and "https", reject promise - // with a TypeError and abort these steps. - if (!script_url.SchemeIsHTTPOrHTTPS()) { - promise_reference->value().Reject(script::kTypeError); - return; - } - - // 4. If any of the strings in scriptURL’s path contains either ASCII - // case-insensitive "%2f" or ASCII case-insensitive "%5c", reject promise - // with a TypeError and abort these steps. - if (PathContainsEscapedSlash(script_url)) { - promise_reference->value().Reject(script::kTypeError); - return; - } - - DCHECK(client); - web::WindowOrWorkerGlobalScope* window_or_worker_global_scope = - client->GetWindowOrWorkerGlobalScope(); - DCHECK(window_or_worker_global_scope); - web::CspDelegate* csp_delegate = - window_or_worker_global_scope->csp_delegate(); - DCHECK(csp_delegate); - if (!csp_delegate->CanLoad(web::CspDelegate::kWorker, script_url, - /* did_redirect*/ false)) { - promise_reference->value().Reject(new web::DOMException( - web::DOMException::kSecurityErr, - "Failed to register a ServiceWorker: The provided scriptURL ('" + - script_url.spec() + "') violates the Content Security Policy.")); - return; - } - - // 5. If scopeURL is null, set scopeURL to the result of parsing the string - // "./" with scriptURL. - GURL scope_url = maybe_scope_url.value_or(script_url.Resolve("./")); - - // 6. If scopeURL is failure, reject promise with a TypeError and abort these - // steps. - if (scope_url.is_empty()) { - promise_reference->value().Reject(script::kTypeError); - return; - } - - // 7. Set scopeURL’s fragment to null. - scope_url = scope_url.ReplaceComponents(replacements); - DCHECK(!scope_url.has_ref() || scope_url.ref().empty()); - DCHECK(!scope_url.is_empty()); - - // 8. If scopeURL’s scheme is not one of "http" and "https", reject promise - // with a TypeError and abort these steps. - if (!scope_url.SchemeIsHTTPOrHTTPS()) { - promise_reference->value().Reject(script::kTypeError); - return; - } - - // 9. If any of the strings in scopeURL’s path contains either ASCII - // case-insensitive "%2f" or ASCII case-insensitive "%5c", reject promise - // with a TypeError and abort these steps. - if (PathContainsEscapedSlash(scope_url)) { - promise_reference->value().Reject(script::kTypeError); - return; - } - - // 10. Let storage key be the result of running obtain a storage key given - // client. - url::Origin storage_key = client->environment_settings()->ObtainStorageKey(); - - // 11. Let job be the result of running Create Job with register, storage key, - // scopeURL, scriptURL, promise, and client. - std::unique_ptr job = - CreateJob(kRegister, storage_key, scope_url, script_url, - JobPromiseType::Create(std::move(promise_reference)), client); - DCHECK(!promise_reference); - - // 12. Set job’s worker type to workerType. - // Cobalt only supports 'classic' worker type. - - // 13. Set job’s update via cache mode to updateViaCache. - job->update_via_cache = update_via_cache; - - // 14. Set job’s referrer to referrer. - // This is the same value as set in CreateJob(). - - // 15. Invoke Schedule Job with job. - ScheduleJob(std::move(job)); - DCHECK(!job.get()); -} +ServiceWorkerJobs::~ServiceWorkerJobs() {} void ServiceWorkerJobs::PromiseErrorData::Reject( std::unique_ptr promise) const { @@ -366,7 +175,7 @@ void ServiceWorkerJobs::ScheduleJob(std::unique_ptr job) { // 5.1. Set job’s containing job queue to jobQueue, and enqueue job to // jobQueue. job->containing_job_queue = job_queue; - if (!IsWebContextRegistered(job->client)) { + if (!service_worker_context_->IsWebContextRegistered(job->client)) { // Note: The client that requested the job has already exited and isn't // able to handle the promise. job->containing_job_queue->PrepareJobForClientShutdown(job, job->client); @@ -396,7 +205,7 @@ void ServiceWorkerJobs::ScheduleJob(std::unique_ptr job) { // 6.3. Else, set job’s containing job queue to jobQueue, and enqueue job to // jobQueue. job->containing_job_queue = job_queue; - if (!IsWebContextRegistered(job->client)) { + if (!service_worker_context_->IsWebContextRegistered(job->client)) { // Note: The client that requested the job has already exited and isn't // able to handle the promise. job->containing_job_queue->PrepareJobForClientShutdown(job, job->client); @@ -546,8 +355,8 @@ void ServiceWorkerJobs::Register(Job* job) { // 4. Let registration be the result of running Get Registration given job’s // storage key and job’s scope url. scoped_refptr registration = - scope_to_registration_map_->GetRegistration(job->storage_key, - job->scope_url); + service_worker_context_->registration_map()->GetRegistration( + job->storage_key, job->scope_url); // 5. If registration is not null, then: if (registration) { @@ -573,7 +382,7 @@ void ServiceWorkerJobs::Register(Job* job) { // 6.1 Invoke Set Registration algorithm with job’s storage key, job’s scope // url, and job’s update via cache mode. - registration = scope_to_registration_map_->SetRegistration( + registration = service_worker_context_->registration_map()->SetRegistration( job->storage_key, job->scope_url, job->update_via_cache); } @@ -581,69 +390,6 @@ void ServiceWorkerJobs::Register(Job* job) { Update(job); } -void ServiceWorkerJobs::SoftUpdate( - ServiceWorkerRegistrationObject* registration, bool force_bypass_cache) { - TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::SoftUpdate()"); - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - DCHECK(registration); - // Algorithm for SoftUpdate: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#soft-update - // 1. Let newestWorker be the result of running Get Newest Worker algorithm - // passing registration as its argument. - ServiceWorkerObject* newest_worker = registration->GetNewestWorker(); - - // 2. If newestWorker is null, abort these steps. - if (newest_worker == nullptr) { - return; - } - - // 3. Let job be the result of running Create Job with update, registration’s - // storage key, registration’s scope url, newestWorker’s script url, null, and - // null. - std::unique_ptr job = CreateJobWithoutPromise( - kUpdate, registration->storage_key(), registration->scope_url(), - newest_worker->script_url()); - - // 4. Set job’s worker type to newestWorker’s type. - // Cobalt only supports 'classic' worker type. - - // 5. Set job’s force bypass cache flag if forceBypassCache is true. - job->force_bypass_cache_flag = force_bypass_cache; - - // 6. Invoke Schedule Job with job. - message_loop()->task_runner()->PostTask( - FROM_HERE, base::BindOnce(&ServiceWorkerJobs::ScheduleJob, - base::Unretained(this), std::move(job))); - DCHECK(!job.get()); -} - -void ServiceWorkerJobs::EnsureServiceWorkerStarted( - const url::Origin& storage_key, const GURL& client_url, - base::WaitableEvent* done_event) { - if (message_loop() != base::MessageLoop::current()) { - message_loop()->task_runner()->PostTask( - FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::EnsureServiceWorkerStarted, - base::Unretained(this), storage_key, client_url, - done_event)); - return; - } - base::ScopedClosureRunner signal_done(base::BindOnce( - [](base::WaitableEvent* done_event) { done_event->Signal(); }, - done_event)); - base::TimeTicks start = base::TimeTicks::Now(); - auto registration = - scope_to_registration_map_->GetRegistration(storage_key, client_url); - if (!registration) { - return; - } - auto service_worker_object = registration->active_worker(); - if (!service_worker_object || service_worker_object->is_running()) { - return; - } - service_worker_object->ObtainWebAgentAndWaitUntilDone(); -} - void ServiceWorkerJobs::Update(Job* job) { TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Update()"); DCHECK_EQ(message_loop(), base::MessageLoop::current()); @@ -654,8 +400,8 @@ void ServiceWorkerJobs::Update(Job* job) { // 1. Let registration be the result of running Get Registration given job’s // storage key and job’s scope url. scoped_refptr registration = - scope_to_registration_map_->GetRegistration(job->storage_key, - job->scope_url); + service_worker_context_->registration_map()->GetRegistration( + job->storage_key, job->scope_url); // 2. If registration is null, then: if (!registration) { @@ -849,12 +595,12 @@ void ServiceWorkerJobs::UpdateOnContentProduced( // 8.19. If response’s cache state is not "local", set registration’s last // update check time to the current time. scoped_refptr registration = - scope_to_registration_map_->GetRegistration(state->job->storage_key, - state->job->scope_url); + service_worker_context_->registration_map()->GetRegistration( + state->job->storage_key, state->job->scope_url); if (registration) { registration->set_last_update_check_time(base::Time::Now()); - scope_to_registration_map_->PersistRegistration(registration->storage_key(), - registration->scope_url()); + service_worker_context_->registration_map()->PersistRegistration( + registration->storage_key(), registration->scope_url()); } // TODO(b/228904017): // 8.20. Set hasUpdatedResources to true if any of the following are true: @@ -892,8 +638,9 @@ void ServiceWorkerJobs::UpdateOnLoadingComplete( DCHECK_EQ(message_loop(), base::MessageLoop::current()); bool check_promise = !state->job->no_promise_okay; if (state->job->no_promise_okay && !state->job->client && - web_context_registrations_.size() > 0) { - state->job->client = *(web_context_registrations_.begin()); + service_worker_context_->web_context_registrations().size() > 0) { + state->job->client = + *(service_worker_context_->web_context_registrations().begin()); } if ((check_promise && !state->job->promise.get()) || !state->job->client) { // The job is already rejected, which means there was an error, or the @@ -908,8 +655,8 @@ void ServiceWorkerJobs::UpdateOnLoadingComplete( state->job, PromiseErrorData(web::DOMException::kSecurityErr, error.value())); if (state->newest_worker == nullptr) { - scope_to_registration_map_->RemoveRegistration(state->job->storage_key, - state->job->scope_url); + service_worker_context_->registration_map()->RemoveRegistration( + state->job->storage_key, state->job->scope_url); } FinishJob(state->job); return; @@ -950,8 +697,8 @@ void ServiceWorkerJobs::UpdateOnLoadingComplete( // 9.2. If newestWorker is null, then remove registration // map[(registration’s storage key, serialized scopeURL)]. if (state->newest_worker == nullptr) { - scope_to_registration_map_->RemoveRegistration(state->job->storage_key, - state->job->scope_url); + service_worker_context_->registration_map()->RemoveRegistration( + state->job->storage_key, state->job->scope_url); } // 9.3. Invoke Finish Job with job and abort these steps. FinishJob(state->job); @@ -978,8 +725,8 @@ void ServiceWorkerJobs::UpdateOnLoadingComplete( "ServiceWorker", state->job->client->web_settings(), state->job->client->network_module(), state->registration); options.web_options.platform_info = state->job->client->platform_info(); - options.web_options.service_worker_jobs = - state->job->client->service_worker_jobs(); + options.web_options.service_worker_context = + state->job->client->service_worker_context(); scoped_refptr worker(new ServiceWorkerObject(options)); // 12. Set worker’s script url to job’s script url, worker’s script // resource to script, worker’s type to job’s worker type, and worker’s @@ -996,7 +743,8 @@ void ServiceWorkerJobs::UpdateOnLoadingComplete( bool force_bypass_cache = state->job->force_bypass_cache_flag; // 16. Let runResult be the result of running the Run Service Worker // algorithm with worker and forceBypassCache. - auto* run_result = RunServiceWorker(worker.get(), force_bypass_cache); + auto* run_result = service_worker_context_->RunServiceWorker( + worker.get(), force_bypass_cache); bool run_result_is_success = run_result; // Post a task for the remaining steps, to let tasks posted by @@ -1017,8 +765,8 @@ void ServiceWorkerJobs::UpdateOnRunServiceWorker( // 17.2. If newestWorker is null, then remove registration // map[(registration’s storage key, serialized scopeURL)]. if (state->newest_worker == nullptr) { - scope_to_registration_map_->RemoveRegistration(state->job->storage_key, - state->job->scope_url); + service_worker_context_->registration_map()->RemoveRegistration( + state->job->storage_key, state->job->scope_url); } // 17.3. Invoke Finish Job with job. FinishJob(state->job); @@ -1029,61 +777,6 @@ void ServiceWorkerJobs::UpdateOnRunServiceWorker( } } -std::string* ServiceWorkerJobs::RunServiceWorker(ServiceWorkerObject* worker, - bool force_bypass_cache) { - TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RunServiceWorker()"); - - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - DCHECK(worker); - // Algorithm for "Run Service Worker" - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm - - // 1. Let unsafeCreationTime be the unsafe shared current time. - auto unsafe_creation_time = base::TimeTicks::Now(); - // 2. If serviceWorker is running, then return serviceWorker’s start status. - if (worker->is_running()) { - return worker->start_status(); - } - // 3. If serviceWorker’s state is "redundant", then return failure. - if (worker->state() == kServiceWorkerStateRedundant) { - return nullptr; - } - // 4. Assert: serviceWorker’s start status is null. - DCHECK(worker->start_status() == nullptr); - // 5. Let script be serviceWorker’s script resource. - // 6. Assert: script is not null. - DCHECK(worker->HasScriptResource()); - // 7. Let startFailed be false. - worker->store_start_failed(false); - // 8. Let agent be the result of obtaining a service worker agent, and run the - // following steps in that context: - // 9. Wait for serviceWorker to be running, or for startFailed to be true. - worker->ObtainWebAgentAndWaitUntilDone(); - // 10. If startFailed is true, then return failure. - if (worker->load_start_failed()) { - return nullptr; - } - // 11. Return serviceWorker’s start status. - return worker->start_status(); -} - -bool ServiceWorkerJobs::WaitForAsynchronousExtensions( - const scoped_refptr& registration) { - // TODO(b/240164388): Investigate a better approach for combining waiting - // for the ExtendableEvent while also allowing use of algorithms that run - // on the same thread from the event handler. - base::TimeTicks wait_start_time = base::TimeTicks::Now(); - do { - if (registration->done_event()->TimedWait( - base::TimeDelta::FromMilliseconds(100))) - break; - base::MessageLoopCurrent::ScopedNestableTaskAllower allow; - base::RunLoop().RunUntilIdle(); - } while ((base::TimeTicks::Now() - wait_start_time) < - kWaitForAsynchronousExtensionsTimeout); - return registration->done_event()->IsSignaled(); -} - void ServiceWorkerJobs::Install( Job* job, const scoped_refptr& worker, const scoped_refptr& registration) { @@ -1109,12 +802,13 @@ void ServiceWorkerJobs::Install( // 4. Run the Update Registration State algorithm passing registration, // "installing" and worker as the arguments. - UpdateRegistrationState(registration, kInstalling, worker); + service_worker_context_->UpdateRegistrationState( + registration, ServiceWorkerContext::kInstalling, worker); // 5. Run the Update Worker State algorithm passing registration’s installing // worker and "installing" as the arguments. - UpdateWorkerState(registration->installing_worker(), - kServiceWorkerStateInstalling); + service_worker_context_->UpdateWorkerState(registration->installing_worker(), + kServiceWorkerStateInstalling); // 6. Assert: job’s job promise is not null. DCHECK(job->no_promise_okay || job->promise.get() != nullptr); // 7. Invoke Resolve Job Promise with job and registration. @@ -1123,7 +817,7 @@ void ServiceWorkerJobs::Install( // registration’s scope url's origin. auto registration_origin = loader::Origin(registration->scope_url()); // 9. For each settingsObject of settingsObjects... - for (auto& context : web_context_registrations_) { + for (auto& context : service_worker_context_->web_context_registrations()) { if (context->environment_settings()->GetOrigin() == registration_origin) { // 9. ... queue a task on settingsObject’s responsible event loop in the // DOM manipulation task source to run the following steps: @@ -1169,7 +863,8 @@ void ServiceWorkerJobs::Install( bool force_bypass_cache = job->force_bypass_cache_flag; // 11.2. If the result of running the Run Service Worker algorithm with // installingWorker and forceBypassCache is failure, then: - auto* run_result = RunServiceWorker(installing_worker, force_bypass_cache); + auto* run_result = service_worker_context_->RunServiceWorker( + installing_worker, force_bypass_cache); if (!run_result) { // 11.2.1. Set installFailed to true. install_failed->store(true); @@ -1233,7 +928,8 @@ void ServiceWorkerJobs::Install( // This waiting is done inside PostBlockingTask above. // 11.3.3. Wait for the step labeled WaitForAsynchronousExtensions to // complete. - if (!WaitForAsynchronousExtensions(registration)) { + if (!service_worker_context_->WaitForAsynchronousExtensions( + registration)) { // Timeout install_failed->store(true); } @@ -1244,17 +940,18 @@ void ServiceWorkerJobs::Install( // 12.1. Run the Update Worker State algorithm passing registration’s // installing worker and "redundant" as the arguments. if (registration->installing_worker()) { - UpdateWorkerState(registration->installing_worker(), - kServiceWorkerStateRedundant); + service_worker_context_->UpdateWorkerState( + registration->installing_worker(), kServiceWorkerStateRedundant); } // 12.2. Run the Update Registration State algorithm passing registration, // "installing" and null as the arguments. - UpdateRegistrationState(registration, kInstalling, nullptr); + service_worker_context_->UpdateRegistrationState( + registration, ServiceWorkerContext::kInstalling, nullptr); // 12.3. If newestWorker is null, then remove registration // map[(registration’s storage key, serialized registration’s // scope url)]. if (newest_worker == nullptr) { - scope_to_registration_map_->RemoveRegistration( + service_worker_context_->registration_map()->RemoveRegistration( registration->storage_key(), registration->scope_url()); } // 12.4. Invoke Finish Job with job and abort these steps. @@ -1270,646 +967,37 @@ void ServiceWorkerJobs::Install( // 16. If registration’s waiting worker is not null, then: if (registration->waiting_worker()) { // 16.1. Terminate registration’s waiting worker. - TerminateServiceWorker(registration->waiting_worker()); + service_worker_context_->TerminateServiceWorker( + registration->waiting_worker()); // 16.2. Run the Update Worker State algorithm passing registration’s // waiting worker and "redundant" as the arguments. - UpdateWorkerState(registration->waiting_worker(), - kServiceWorkerStateRedundant); + service_worker_context_->UpdateWorkerState(registration->waiting_worker(), + kServiceWorkerStateRedundant); } // 17. Run the Update Registration State algorithm passing registration, // "waiting" and registration’s installing worker as the arguments. - UpdateRegistrationState(registration, kWaiting, - registration->installing_worker()); + service_worker_context_->UpdateRegistrationState( + registration, ServiceWorkerContext::kWaiting, + registration->installing_worker()); // 18. Run the Update Registration State algorithm passing registration, // "installing" and null as the arguments. - UpdateRegistrationState(registration, kInstalling, nullptr); + service_worker_context_->UpdateRegistrationState( + registration, ServiceWorkerContext::kInstalling, nullptr); // 19. Run the Update Worker State algorithm passing registration’s waiting // worker and "installed" as the arguments. - UpdateWorkerState(registration->waiting_worker(), - kServiceWorkerStateInstalled); + service_worker_context_->UpdateWorkerState(registration->waiting_worker(), + kServiceWorkerStateInstalled); // 20. Invoke Finish Job with job. FinishJob(job); // 21. Wait for all the tasks queued by Update Worker State invoked in this // algorithm to have executed. // TODO(b/234788479): Wait for tasks. // 22. Invoke Try Activate with registration. - TryActivate(registration); + service_worker_context_->TryActivate(registration); // Persist registration since the waiting_worker has been updated. - scope_to_registration_map_->PersistRegistration(registration->storage_key(), - registration->scope_url()); -} - -bool ServiceWorkerJobs::IsAnyClientUsingRegistration( - ServiceWorkerRegistrationObject* registration) { - bool any_client_is_using = false; - for (auto& context : web_context_registrations_) { - // When a service worker client is controlled by a service worker, it is - // said that the service worker client is using the service worker’s - // containing service worker registration. - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control - if (context->is_controlled_by(registration->active_worker())) { - any_client_is_using = true; - break; - } - } - return any_client_is_using; -} - -void ServiceWorkerJobs::TryActivate( - ServiceWorkerRegistrationObject* registration) { - TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TryActivate()"); - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - // Algorithm for Try Activate: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm - - // 1. If registration’s waiting worker is null, return. - if (!registration) return; - if (!registration->waiting_worker()) return; - - // 2. If registration’s active worker is not null and registration’s active - // worker's state is "activating", return. - if (registration->active_worker() && - (registration->active_worker()->state() == kServiceWorkerStateActivating)) - return; - - // 3. Invoke Activate with registration if either of the following is true: - - // - registration’s active worker is null. - bool invoke_activate = registration->active_worker() == nullptr; - - if (!invoke_activate) { - // - The result of running Service Worker Has No Pending Events with - // registration’s active worker is true... - if (ServiceWorkerHasNoPendingEvents(registration->active_worker())) { - // ... and no service worker client is using registration... - bool any_client_using = IsAnyClientUsingRegistration(registration); - invoke_activate = !any_client_using; - // ... or registration’s waiting worker's skip waiting flag is - // set. - if (!invoke_activate && registration->waiting_worker()->skip_waiting()) - invoke_activate = true; - } - } - - if (invoke_activate) Activate(registration); -} - -void ServiceWorkerJobs::Activate( - ServiceWorkerRegistrationObject* registration) { - TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Activate()"); - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - // Algorithm for Activate: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm - - // 1. If registration’s waiting worker is null, abort these steps. - if (registration->waiting_worker() == nullptr) return; - // 2. If registration’s active worker is not null, then: - if (registration->active_worker()) { - // 2.1. Terminate registration’s active worker. - TerminateServiceWorker(registration->active_worker()); - // 2.2. Run the Update Worker State algorithm passing registration’s active - // worker and "redundant" as the arguments. - UpdateWorkerState(registration->active_worker(), - kServiceWorkerStateRedundant); - } - // 3. Run the Update Registration State algorithm passing registration, - // "active" and registration’s waiting worker as the arguments. - UpdateRegistrationState(registration, kActive, - registration->waiting_worker()); - // 4. Run the Update Registration State algorithm passing registration, - // "waiting" and null as the arguments. - UpdateRegistrationState(registration, kWaiting, nullptr); - // 5. Run the Update Worker State algorithm passing registration’s active - // worker and "activating" as the arguments. - UpdateWorkerState(registration->active_worker(), - kServiceWorkerStateActivating); - // 6. Let matchedClients be a list of service worker clients whose creation - // URL matches registration’s storage key and registration’s scope url. - std::list matched_clients; - for (auto& context : web_context_registrations_) { - url::Origin context_storage_key = - url::Origin::Create(context->environment_settings()->creation_url()); - scoped_refptr matched_registration = - scope_to_registration_map_->MatchServiceWorkerRegistration( - context_storage_key, registration->scope_url()); - if (matched_registration == registration) { - matched_clients.push_back(context); - } - } - // 7. For each client of matchedClients, queue a task on client’s responsible - // event loop, using the DOM manipulation task source, to run the following - // substeps: - for (auto& client : matched_clients) { - // 7.1. Let readyPromise be client’s global object's - // ServiceWorkerContainer object’s ready - // promise. - // 7.2. If readyPromise is null, then continue. - // 7.3. If readyPromise is pending, resolve - // readyPromise with the the result of getting - // the service worker registration object that - // represents registration in readyPromise’s - // relevant settings object. - client->message_loop()->task_runner()->PostTask( - FROM_HERE, - base::BindOnce(&ServiceWorkerContainer::MaybeResolveReadyPromise, - base::Unretained(client->GetWindowOrWorkerGlobalScope() - ->navigator_base() - ->service_worker() - .get()), - base::Unretained(registration))); - } - // 8. For each client of matchedClients: - // 8.1. If client is a window client, unassociate client’s responsible - // document from its application cache, if it has one. - // 8.2. Else if client is a shared worker client, unassociate client’s - // global object from its application cache, if it has one. - // Cobalt doesn't implement 'application cache': - // https://www.w3.org/TR/2011/WD-html5-20110525/offline.html#applicationcache - // 9. For each service worker client client who is using registration: - // Note: The spec defines "control" and "use" of a service worker from the - // value of the active service worker property of the client environment, but - // that property is set here, so here we should not use that exact definition - // to determine if the client is using this registration. Instead, we use the - // Match Service Worker Registration algorithm to find the registration for a - // client and compare it with the registration being activated. - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-use - for (const auto& client : web_context_registrations_) { - scoped_refptr client_registration = - scope_to_registration_map_->MatchServiceWorkerRegistration( - client->environment_settings()->ObtainStorageKey(), - client->environment_settings()->creation_url()); - // When a service worker client is controlled by a service worker, it is - // said that the service worker client is using the service worker’s - // containing service worker registration. - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-control - if (client_registration.get() == registration) { - // 9.1. Set client’s active worker to registration’s active worker. - client->set_active_service_worker(registration->active_worker()); - // 9.2. Invoke Notify Controller Change algorithm with client as the - // argument. - NotifyControllerChange(client); - } - } - // 10. Let activeWorker be registration’s active worker. - ServiceWorkerObject* active_worker = registration->active_worker(); - bool activated = true; - // 11. If the result of running the Should Skip Event algorithm with - // activeWorker and "activate" is false, then: - DCHECK(active_worker); - if (!active_worker->ShouldSkipEvent(base::Tokens::activate())) { - // 11.1. If the result of running the Run Service Worker algorithm with - // activeWorker is not failure, then: - auto* run_result = RunServiceWorker(active_worker); - if (run_result) { - // 11.1.1. Queue a task task on activeWorker’s event loop using the DOM - // manipulation task source to run the following steps: - DCHECK_EQ(active_worker->web_agent()->context(), - active_worker->worker_global_scope() - ->environment_settings() - ->context()); - DCHECK(registration->done_event()->IsSignaled()); - registration->done_event()->Reset(); - active_worker->web_agent() - ->context() - ->message_loop() - ->task_runner() - ->PostBlockingTask( - FROM_HERE, - base::Bind( - [](ServiceWorkerObject* active_worker, - base::WaitableEvent* done_event) { - auto done_callback = - base::BindOnce([](base::WaitableEvent* done_event, - bool) { done_event->Signal(); }, - done_event); - auto* settings = active_worker->web_agent() - ->context() - ->environment_settings(); - scoped_refptr event( - new ExtendableEvent(settings, base::Tokens::activate(), - std::move(done_callback))); - // 11.1.1.1. Let e be the result of creating an event with - // ExtendableEvent. - // 11.1.1.2. Initialize e’s type attribute to activate. - // 11.1.1.3. Dispatch e at activeWorker’s global object. - active_worker->worker_global_scope()->DispatchEvent(event); - // 11.1.1.4. WaitForAsynchronousExtensions: Wait, in - // parallel, until e is not active. - if (!event->IsActive()) { - // If the event handler doesn't use waitUntil(), it will - // already no longer be active, and there will never be a - // callback to signal the done event. - done_event->Signal(); - } - }, - base::Unretained(active_worker), registration->done_event())); - // 11.1.2. Wait for task to have executed or been discarded. - // This waiting is done inside PostBlockingTask above. - // 11.1.3. Wait for the step labeled WaitForAsynchronousExtensions to - // complete. - // TODO(b/240164388): Investigate a better approach for combining waiting - // for the ExtendableEvent while also allowing use of algorithms that run - // on the same thread from the event handler. - if (!WaitForAsynchronousExtensions(registration)) { - // Timeout - activated = false; - } - } else { - activated = false; - } - } - // 12. Run the Update Worker State algorithm passing registration’s active - // worker and "activated" as the arguments. - if (activated && registration->active_worker()) { - UpdateWorkerState(registration->active_worker(), - kServiceWorkerStateActivated); - - // Persist registration since the waiting_worker has been updated to nullptr - // and the active_worker has been updated to the previous waiting_worker. - scope_to_registration_map_->PersistRegistration(registration->storage_key(), - registration->scope_url()); - } -} - -void ServiceWorkerJobs::NotifyControllerChange(web::Context* client) { - // Algorithm for Notify Controller Change: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm - // 1. Assert: client is not null. - DCHECK(client); - - // 2. If client is an environment settings object, queue a task to fire an - // event named controllerchange at the ServiceWorkerContainer object that - // client is associated with. - client->message_loop()->task_runner()->PostTask( - FROM_HERE, base::Bind( - [](web::Context* client) { - client->GetWindowOrWorkerGlobalScope() - ->navigator_base() - ->service_worker() - ->DispatchEvent(new web::Event( - base::Tokens::controllerchange())); - }, - client)); -} - -bool ServiceWorkerJobs::ServiceWorkerHasNoPendingEvents( - ServiceWorkerObject* worker) { - // Algorithm for Service Worker Has No Pending Events - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events - // TODO(b/240174245): Implement this using the 'set of extended events'. - NOTIMPLEMENTED(); - - // 1. For each event of worker’s set of extended events: - // 1.1. If event is active, return false. - // 2. Return true. - return true; -} - -void ServiceWorkerJobs::ClearRegistration( - ServiceWorkerRegistrationObject* registration) { - TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ClearRegistration()"); - // Algorithm for Clear Registration: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm - // 1. Run the following steps atomically. - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - - // 2. If registration’s installing worker is not null, then: - ServiceWorkerObject* installing_worker = registration->installing_worker(); - if (installing_worker) { - // 2.1. Terminate registration’s installing worker. - TerminateServiceWorker(installing_worker); - // 2.2. Run the Update Worker State algorithm passing registration’s - // installing worker and "redundant" as the arguments. - UpdateWorkerState(installing_worker, kServiceWorkerStateRedundant); - // 2.3. Run the Update Registration State algorithm passing registration, - // "installing" and null as the arguments. - UpdateRegistrationState(registration, kInstalling, nullptr); - } - - // 3. If registration’s waiting worker is not null, then: - ServiceWorkerObject* waiting_worker = registration->waiting_worker(); - if (waiting_worker) { - // 3.1. Terminate registration’s waiting worker. - TerminateServiceWorker(waiting_worker); - // 3.2. Run the Update Worker State algorithm passing registration’s - // waiting worker and "redundant" as the arguments. - UpdateWorkerState(waiting_worker, kServiceWorkerStateRedundant); - // 3.3. Run the Update Registration State algorithm passing registration, - // "waiting" and null as the arguments. - UpdateRegistrationState(registration, kWaiting, nullptr); - } - - // 4. If registration’s active worker is not null, then: - ServiceWorkerObject* active_worker = registration->active_worker(); - if (active_worker) { - // 4.1. Terminate registration’s active worker. - TerminateServiceWorker(active_worker); - // 4.2. Run the Update Worker State algorithm passing registration’s - // active worker and "redundant" as the arguments. - UpdateWorkerState(active_worker, kServiceWorkerStateRedundant); - // 4.3. Run the Update Registration State algorithm passing registration, - // "active" and null as the arguments. - UpdateRegistrationState(registration, kActive, nullptr); - } - - // Persist registration since the waiting_worker and active_worker have - // been updated to nullptr. This will remove any persisted registration - // if one exists. - scope_to_registration_map_->PersistRegistration(registration->storage_key(), - registration->scope_url()); -} - -void ServiceWorkerJobs::TryClearRegistration( - ServiceWorkerRegistrationObject* registration) { - TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TryClearRegistration()"); - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - // Algorithm for Try Clear Registration: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm - - // 1. Invoke Clear Registration with registration if no service worker client - // is using registration and all of the following conditions are true: - if (IsAnyClientUsingRegistration(registration)) return; - - // . registration’s installing worker is null or the result of running - // Service Worker Has No Pending Events with registration’s installing - // worker is true. - if (registration->installing_worker() && - !ServiceWorkerHasNoPendingEvents(registration->installing_worker())) - return; - - // . registration’s waiting worker is null or the result of running - // Service Worker Has No Pending Events with registration’s waiting - // worker is true. - if (registration->waiting_worker() && - !ServiceWorkerHasNoPendingEvents(registration->waiting_worker())) - return; - - // . registration’s active worker is null or the result of running - // ServiceWorker Has No Pending Events with registration’s active worker - // is true. - if (registration->active_worker() && - !ServiceWorkerHasNoPendingEvents(registration->active_worker())) - return; - - ClearRegistration(registration); -} - -void ServiceWorkerJobs::UpdateRegistrationState( - ServiceWorkerRegistrationObject* registration, RegistrationState target, - const scoped_refptr& source) { - TRACE_EVENT2("cobalt::worker", "ServiceWorkerJobs::UpdateRegistrationState()", - "target", target, "source", source); - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - DCHECK(registration); - // Algorithm for Update Registration State: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm - - // 1. Let registrationObjects be an array containing all the - // ServiceWorkerRegistration objects associated with registration. - // This is implemented with a call to LookupServiceWorkerRegistration for each - // registered web context. - - switch (target) { - // 2. If target is "installing", then: - case kInstalling: { - // 2.1. Set registration’s installing worker to source. - registration->set_installing_worker(source); - // 2.2. For each registrationObject in registrationObjects: - for (auto& context : web_context_registrations_) { - // 2.2.1. Queue a task to... - context->message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, - base::Bind( - [](web::Context* context, - ServiceWorkerRegistrationObject* registration) { - // 2.2.1. ... set the installing attribute of - // registrationObject to null if registration’s - // installing worker is null, or the result of getting - // the service worker object that represents - // registration’s installing worker in - // registrationObject’s relevant settings object. - auto registration_object = - context->LookupServiceWorkerRegistration(registration); - if (registration_object) { - registration_object->set_installing( - context->GetServiceWorker( - registration->installing_worker())); - } - }, - context, base::Unretained(registration))); - } - break; - } - // 3. Else if target is "waiting", then: - case kWaiting: { - // 3.1. Set registration’s waiting worker to source. - registration->set_waiting_worker(source); - // 3.2. For each registrationObject in registrationObjects: - for (auto& context : web_context_registrations_) { - // 3.2.1. Queue a task to... - context->message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, - base::Bind( - [](web::Context* context, - ServiceWorkerRegistrationObject* registration) { - // 3.2.1. ... set the waiting attribute of registrationObject - // to null if registration’s waiting worker is null, or - // the result of getting the service worker object that - // represents registration’s waiting worker in - // registrationObject’s relevant settings object. - auto registration_object = - context->LookupServiceWorkerRegistration(registration); - if (registration_object) { - registration_object->set_waiting(context->GetServiceWorker( - registration->waiting_worker())); - } - }, - context, base::Unretained(registration))); - } - break; - } - // 4. Else if target is "active", then: - case kActive: { - // 4.1. Set registration’s active worker to source. - registration->set_active_worker(source); - // 4.2. For each registrationObject in registrationObjects: - for (auto& context : web_context_registrations_) { - // 4.2.1. Queue a task to... - context->message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, - base::Bind( - [](web::Context* context, - ServiceWorkerRegistrationObject* registration) { - // 4.2.1. ... set the active attribute of registrationObject - // to null if registration’s active worker is null, or - // the result of getting the service worker object that - // represents registration’s active worker in - // registrationObject’s relevant settings object. - auto registration_object = - context->LookupServiceWorkerRegistration(registration); - if (registration_object) { - registration_object->set_active(context->GetServiceWorker( - registration->active_worker())); - } - }, - context, base::Unretained(registration))); - } - break; - } - default: - NOTREACHED(); - } -} - -void ServiceWorkerJobs::UpdateWorkerState(ServiceWorkerObject* worker, - ServiceWorkerState state) { - TRACE_EVENT1("cobalt::worker", "ServiceWorkerJobs::UpdateWorkerState()", - "state", state); - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - DCHECK(worker); - if (!worker) { - return; - } - // Algorithm for Update Worker State: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm - // 1. Assert: state is not "parsed". - DCHECK_NE(kServiceWorkerStateParsed, state); - // 2. Set worker's state to state. - worker->set_state(state); - auto worker_origin = loader::Origin(worker->script_url()); - // 3. Let settingsObjects be all environment settings objects whose origin is - // worker's script url's origin. - // 4. For each settingsObject of settingsObjects... - for (auto& context : web_context_registrations_) { - if (context->environment_settings()->GetOrigin() == worker_origin) { - // 4. ... queue a task on - // settingsObject's responsible event loop in the DOM manipulation task - // source to run the following steps: - context->message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, base::Bind( - [](web::Context* context, ServiceWorkerObject* worker, - ServiceWorkerState state) { - DCHECK_EQ(context->message_loop(), - base::MessageLoop::current()); - // 4.1. Let objectMap be settingsObject's service - // worker object - // map. - // 4.2. If objectMap[worker] does not exist, then - // abort these - // steps. - // 4.3. Let workerObj be objectMap[worker]. - auto worker_obj = - context->LookupServiceWorker(worker); - if (worker_obj) { - // 4.4. Set workerObj's state to state. - worker_obj->set_state(state); - // 4.5. Fire an event named statechange at - // workerObj. - worker_obj->DispatchEvent( - new web::Event(base::Tokens::statechange())); - } - }, - context, base::Unretained(worker), state)); - } - } -} - -void ServiceWorkerJobs::HandleServiceWorkerClientUnload(web::Context* client) { - TRACE_EVENT0("cobalt::worker", - "ServiceWorkerJobs::HandleServiceWorkerClientUnload()"); - // Algorithm for Handle Servicer Worker Client Unload: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm - DCHECK(client); - // 1. Run the following steps atomically. - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - - // 2. Let registration be the service worker registration used by client. - // 3. If registration is null, abort these steps. - ServiceWorkerObject* active_service_worker = client->active_service_worker(); - if (!active_service_worker) return; - ServiceWorkerRegistrationObject* registration = - active_service_worker->containing_service_worker_registration(); - if (!registration) return; - - // 4. If any other service worker client is using registration, abort these - // steps. - // Ensure the client is already removed from the registrations when this runs. - DCHECK(web_context_registrations_.end() == - web_context_registrations_.find(client)); - if (IsAnyClientUsingRegistration(registration)) return; - - // 5. If registration is unregistered, invoke Try Clear Registration with - // registration. - if (scope_to_registration_map_ && - scope_to_registration_map_->IsUnregistered(registration)) { - TryClearRegistration(registration); - } - - // 6. Invoke Try Activate with registration. - TryActivate(registration); -} - -void ServiceWorkerJobs::TerminateServiceWorker(ServiceWorkerObject* worker) { - TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TerminateServiceWorker()"); - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - // Algorithm for Terminate Service Worker: - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker - // 1. Run the following steps in parallel with serviceWorker’s main loop: - // This runs in the ServiceWorkerRegistry thread. - DCHECK_EQ(message_loop(), base::MessageLoop::current()); - - // 1.1. Let serviceWorkerGlobalScope be serviceWorker’s global object. - WorkerGlobalScope* service_worker_global_scope = - worker->worker_global_scope(); - - // 1.2. Set serviceWorkerGlobalScope’s closing flag to true. - if (service_worker_global_scope != nullptr) - service_worker_global_scope->set_closing_flag(true); - - // 1.3. Remove all the items from serviceWorker’s set of extended events. - // TODO(b/240174245): Implement 'set of extended events'. - - // 1.4. If there are any tasks, whose task source is either the handle fetch - // task source or the handle functional event task source, queued in - // serviceWorkerGlobalScope’s event loop’s task queues, queue them to - // serviceWorker’s containing service worker registration’s corresponding - // task queues in the same order using their original task sources, and - // discard all the tasks (including tasks whose task source is neither - // the handle fetch task source nor the handle functional event task - // source) from serviceWorkerGlobalScope’s event loop’s task queues - // without processing them. - // TODO(b/234787641): Queue tasks to the registration. - - // Note: This step is not in the spec, but without this step the service - // worker object map will always keep an entry with a service worker instance - // for the terminated service worker, which besides leaking memory can lead to - // unexpected behavior when new service worker objects are created with the - // same key for the service worker object map (which in Cobalt's case - // happens when a new service worker object is constructed at the same - // memory address). - for (auto& context : web_context_registrations_) { - context->message_loop()->task_runner()->PostBlockingTask( - FROM_HERE, base::Bind( - [](web::Context* context, ServiceWorkerObject* worker) { - auto worker_obj = context->LookupServiceWorker(worker); - if (worker_obj) { - worker_obj->set_state(kServiceWorkerStateRedundant); - worker_obj->DispatchEvent( - new web::Event(base::Tokens::statechange())); - } - context->RemoveServiceWorker(worker); - }, - context, base::Unretained(worker))); - } - - // 1.5. Abort the script currently running in serviceWorker. - if (worker->is_running()) { - worker->Abort(); - } - - // 1.6. Set serviceWorker’s start status to null. - worker->set_start_status(nullptr); + service_worker_context_->registration_map()->PersistRegistration( + registration->storage_key(), registration->scope_url()); } void ServiceWorkerJobs::Unregister(Job* job) { @@ -1938,8 +1026,8 @@ void ServiceWorkerJobs::Unregister(Job* job) { // 2. Let registration be the result of running Get Registration given job’s // storage key and job’s scope url. scoped_refptr registration = - scope_to_registration_map_->GetRegistration(job->storage_key, - job->scope_url); + service_worker_context_->registration_map()->GetRegistration( + job->storage_key, job->scope_url); // 3. If registration is null, then: if (!registration) { @@ -1953,14 +1041,14 @@ void ServiceWorkerJobs::Unregister(Job* job) { // 4. Remove registration map[(registration’s storage key, job’s scope url)]. // Keep the registration until this algorithm finishes. - scope_to_registration_map_->RemoveRegistration(registration->storage_key(), - job->scope_url); + service_worker_context_->registration_map()->RemoveRegistration( + registration->storage_key(), job->scope_url); // 5. Invoke Resolve Job Promise with job and true. ResolveJobPromise(job, true); // 6. Invoke Try Clear Registration with registration. - TryClearRegistration(registration); + service_worker_context_->TryClearRegistration(registration); // 7. Invoke Finish Job with job. FinishJob(job); @@ -1983,7 +1071,7 @@ void ServiceWorkerJobs::RejectJobPromise(Job* job, // job promise with a new exception with errorData and a user // agent-defined message, in equivalentJob’s client's Realm. if (job->client && job->promise != nullptr) { - DCHECK(IsWebContextRegistered(job->client)); + DCHECK(service_worker_context_->IsWebContextRegistered(job->client)); job->client->message_loop()->task_runner()->PostTask( FROM_HERE, base::BindOnce( [](std::unique_ptr promise, @@ -2018,7 +1106,7 @@ void ServiceWorkerJobs::ResolveJobPromise( // 2.1 If equivalentJob’s client is null, continue to the next iteration of // the loop. if (job->client && job->promise != nullptr) { - DCHECK(IsWebContextRegistered(job->client)); + DCHECK(service_worker_context_->IsWebContextRegistered(job->client)); job->client->message_loop()->task_runner()->PostTask( FROM_HERE, base::BindOnce( @@ -2082,6 +1170,7 @@ void ServiceWorkerJobs::FinishJob(Job* job) { } } +<<<<<<< HEAD void ServiceWorkerJobs::MaybeResolveReadyPromiseSubSteps(web::Context* client) { DCHECK_EQ(message_loop(), base::MessageLoop::current()); // Algorithm for Sub steps of ServiceWorkerContainer.ready(): @@ -2860,6 +1949,8 @@ void ServiceWorkerJobs::UnregisterWebContext(web::Context* context) { } } +======= +>>>>>>> 6e280c8f020 (Moving service worker job related code to its own file. (#628)) void ServiceWorkerJobs::PrepareForClientShutdown(web::Context* client) { DCHECK(client); if (!client) return; diff --git a/cobalt/worker/service_worker_jobs.h b/cobalt/worker/service_worker_jobs.h index ffc80d903cd9..5460edc4a362 100644 --- a/cobalt/worker/service_worker_jobs.h +++ b/cobalt/worker/service_worker_jobs.h @@ -18,16 +18,13 @@ #include #include #include -#include #include #include -#include "base/memory/ref_counted.h" #include "base/memory/scoped_refptr.h" #include "base/message_loop/message_loop.h" #include "base/optional.h" #include "base/synchronization/lock.h" -#include "base/synchronization/waitable_event.h" #include "base/task/sequence_manager/moveable_auto_lock.h" #include "cobalt/loader/fetcher_factory.h" #include "cobalt/loader/script_loader_factory.h" @@ -38,17 +35,9 @@ #include "cobalt/script/script_value_factory.h" #include "cobalt/web/context.h" #include "cobalt/web/dom_exception.h" -#include "cobalt/web/web_settings.h" -#include "cobalt/worker/client_query_options.h" -#include "cobalt/worker/frame_type.h" -#include "cobalt/worker/service_worker.h" -#include "cobalt/worker/service_worker_consts.h" #include "cobalt/worker/service_worker_object.h" -#include "cobalt/worker/service_worker_registration.h" -#include "cobalt/worker/service_worker_registration_map.h" #include "cobalt/worker/service_worker_registration_object.h" #include "cobalt/worker/service_worker_update_via_cache.h" -#include "cobalt/worker/worker_type.h" #include "starboard/common/atomic.h" #include "url/gurl.h" #include "url/origin.h" @@ -56,6 +45,8 @@ namespace cobalt { namespace worker { +class ServiceWorkerContext; + // Algorithms for Service Worker Jobs. // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#algorithms class ServiceWorkerJobs { @@ -189,14 +180,14 @@ class ServiceWorkerJobs { std::deque> jobs_; }; - ServiceWorkerJobs(web::WebSettings* web_settings, + ServiceWorkerJobs(ServiceWorkerContext* service_worker_context, network::NetworkModule* network_module, - web::UserAgentPlatformInfo* platform_info, - base::MessageLoop* message_loop, const GURL& url); + base::MessageLoop* message_loop); ~ServiceWorkerJobs(); base::MessageLoop* message_loop() { return message_loop_; } +<<<<<<< HEAD // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#start-register-algorithm void StartRegister(const base::Optional& scope_url, const GURL& script_url, @@ -277,13 +268,12 @@ class ServiceWorkerJobs { web_context_registrations_.find(context); } +======= +>>>>>>> 6e280c8f020 (Moving service worker job related code to its own file. (#628)) // Ensure no references are kept to JS objects for a client that is about to // be shutdown. void PrepareForClientShutdown(web::Context* client); - // Set the active worker for a client if there is a matching service worker. - void SetActiveWorker(web::EnvironmentSettings* client); - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-job std::unique_ptr CreateJob( JobType type, const url::Origin& storage_key, const GURL& scope_url, @@ -318,21 +308,9 @@ class ServiceWorkerJobs { // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#schedule-job void ScheduleJob(std::unique_ptr job); - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#activation-algorithm - void Activate(ServiceWorkerRegistrationObject* registration); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#clear-registration-algorithm - void ClearRegistration(ServiceWorkerRegistrationObject* registration); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#soft-update - void SoftUpdate(ServiceWorkerRegistrationObject* registration, - bool force_bypass_cache); - - void EnsureServiceWorkerStarted(const url::Origin& storage_key, - const GURL& client_url, - base::WaitableEvent* done_event); - private: + friend class ServiceWorkerContext; + // State used for the 'Update' algorithm. struct UpdateJobState : public base::RefCounted { UpdateJobState( @@ -380,8 +358,6 @@ class ServiceWorkerJobs { const std::string message_; }; - enum RegistrationState { kInstalling, kWaiting, kActive }; - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-equivalent bool ReturnJobsAreEquivalent(Job* one, Job* two); @@ -428,66 +404,21 @@ class ServiceWorkerJobs { // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#finish-job-algorithm void FinishJob(Job* job); - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-service-worker-algorithm - // The return value is a 'Completion or failure'. - // A failure is signaled by returning nullptr. Otherwise, the returned string - // points to the value of the Completion returned by the script runner - // abstraction. - std::string* RunServiceWorker(ServiceWorkerObject* worker, - bool force_bypass_cache = false); - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#installation-algorithm void Install( Job* job, const scoped_refptr& worker, const scoped_refptr& registration); - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-activate-algorithm - void TryActivate(ServiceWorkerRegistrationObject* registration); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#service-worker-has-no-pending-events - bool ServiceWorkerHasNoPendingEvents(ServiceWorkerObject* worker); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-registration-state-algorithm - void UpdateRegistrationState( - ServiceWorkerRegistrationObject* registration, RegistrationState target, - const scoped_refptr& source); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-state-algorithm - void UpdateWorkerState(ServiceWorkerObject* worker, ServiceWorkerState state); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-client-unload-algorithm - void HandleServiceWorkerClientUnload(web::Context* client); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#terminate-service-worker - void TerminateServiceWorker(ServiceWorkerObject* worker); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#notify-controller-change-algorithm - void NotifyControllerChange(web::Context* client); - - // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#try-clear-registration-algorithm - void TryClearRegistration(ServiceWorkerRegistrationObject* registration); - - bool IsAnyClientUsingRegistration( - ServiceWorkerRegistrationObject* registration); - - // Returns false when the timeout is reached. - bool WaitForAsynchronousExtensions( - const scoped_refptr& registration); + ServiceWorkerContext* service_worker_context_; // FetcherFactory that is used to create a fetcher according to URL. std::unique_ptr fetcher_factory_; // LoaderFactory that is used to acquire references to resources from a URL. std::unique_ptr script_loader_factory_; + base::MessageLoop* message_loop_; JobQueueMap job_queue_map_; - std::unique_ptr scope_to_registration_map_; - - std::set web_context_registrations_; - - base::WaitableEvent web_context_registrations_cleared_ = { - base::WaitableEvent::ResetPolicy::MANUAL, - base::WaitableEvent::InitialState::NOT_SIGNALED}; }; } // namespace worker diff --git a/cobalt/worker/service_worker_persistent_settings.cc b/cobalt/worker/service_worker_persistent_settings.cc index 41a689d6f294..f74d63fe14c6 100644 --- a/cobalt/worker/service_worker_persistent_settings.cc +++ b/cobalt/worker/service_worker_persistent_settings.cc @@ -31,6 +31,7 @@ #include "cobalt/script/script_value.h" #include "cobalt/web/cache_utils.h" #include "cobalt/worker/service_worker_consts.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" @@ -171,20 +172,20 @@ void ServiceWorkerPersistentSettings::ReadServiceWorkerRegistrationMapSettings( registration_map.insert(std::make_pair(key, registration)); registration->set_is_persisted(true); - options_.service_worker_jobs->message_loop()->task_runner()->PostTask( + options_.service_worker_context->message_loop()->task_runner()->PostTask( FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::Activate, - base::Unretained(options_.service_worker_jobs), + base::BindOnce(&ServiceWorkerContext::Activate, + base::Unretained(options_.service_worker_context), registration)); - auto job = options_.service_worker_jobs->CreateJobWithoutPromise( + auto job = options_.service_worker_context->jobs()->CreateJobWithoutPromise( ServiceWorkerJobs::JobType::kUpdate, storage_key, scope, registration->waiting_worker()->script_url()); - options_.service_worker_jobs->message_loop()->task_runner()->PostTask( - FROM_HERE, - base::BindOnce(&ServiceWorkerJobs::ScheduleJob, - base::Unretained(options_.service_worker_jobs), - std::move(job))); + 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))); } } @@ -199,7 +200,7 @@ bool ServiceWorkerPersistentSettings::ReadServiceWorkerObjectSettings( options_.web_settings, options_.network_module, registration); options.web_options.platform_info = options_.platform_info; - options.web_options.service_worker_jobs = options_.service_worker_jobs; + options.web_options.service_worker_context = options_.service_worker_context; scoped_refptr worker(new ServiceWorkerObject(options)); base::Value* script_url_value = value_dict->FindKeyOfType( diff --git a/cobalt/worker/service_worker_persistent_settings.h b/cobalt/worker/service_worker_persistent_settings.h index 1d4fd2ecd763..e595acbe69d1 100644 --- a/cobalt/worker/service_worker_persistent_settings.h +++ b/cobalt/worker/service_worker_persistent_settings.h @@ -48,16 +48,16 @@ class ServiceWorkerPersistentSettings { Options(web::WebSettings* web_settings, network::NetworkModule* network_module, web::UserAgentPlatformInfo* platform_info, - ServiceWorkerJobs* service_worker_jobs, const GURL& url) + ServiceWorkerContext* service_worker_context, const GURL& url) : web_settings(web_settings), network_module(network_module), platform_info(platform_info), - service_worker_jobs(service_worker_jobs), + service_worker_context(service_worker_context), url(url) {} web::WebSettings* web_settings; network::NetworkModule* network_module; web::UserAgentPlatformInfo* platform_info; - ServiceWorkerJobs* service_worker_jobs; + ServiceWorkerContext* service_worker_context; const GURL& url; }; diff --git a/cobalt/worker/service_worker_registration.cc b/cobalt/worker/service_worker_registration.cc index adba128265a1..514463a203ed 100644 --- a/cobalt/worker/service_worker_registration.cc +++ b/cobalt/worker/service_worker_registration.cc @@ -27,6 +27,7 @@ #include "cobalt/web/environment_settings.h" #include "cobalt/web/window_or_worker_global_scope.h" #include "cobalt/worker/service_worker.h" +#include "cobalt/worker/service_worker_context.h" #include "cobalt/worker/service_worker_global_scope.h" #include "cobalt/worker/service_worker_jobs.h" #include "cobalt/worker/service_worker_registration_object.h" @@ -110,20 +111,18 @@ void ServiceWorkerRegistration::UpdateTask( // 6. Let job be the result of running Create Job with update, registration’s // storage key, registration’s scope url, newestWorker’s script url, // promise, and this's relevant settings object. - worker::ServiceWorkerJobs* jobs = - environment_settings()->context()->service_worker_jobs(); + ServiceWorkerJobs* jobs = + environment_settings()->context()->service_worker_context()->jobs(); std::unique_ptr job = jobs->CreateJob( ServiceWorkerJobs::JobType::kUpdate, registration_->storage_key(), registration_->scope_url(), newest_worker->script_url(), std::move(promise_reference), environment_settings()->context()); - DCHECK(!promise_reference); // 7. Set job’s worker type to newestWorker’s type. // Cobalt only supports 'classic' worker type. // 8. Invoke Schedule Job with job. jobs->ScheduleJob(std::move(job)); - DCHECK(!job.get()); } script::HandlePromiseBool ServiceWorkerRegistration::Unregister() { @@ -146,7 +145,7 @@ script::HandlePromiseBool ServiceWorkerRegistration::Unregister() { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce( - [](worker::ServiceWorkerJobs* jobs, const url::Origin& storage_key, + [](ServiceWorkerJobs* jobs, const url::Origin& storage_key, const GURL& scope_url, std::unique_ptr promise_reference, @@ -163,7 +162,7 @@ script::HandlePromiseBool ServiceWorkerRegistration::Unregister() { jobs->ScheduleJob(std::move(job)); DCHECK(!job.get()); }, - environment_settings()->context()->service_worker_jobs(), + environment_settings()->context()->service_worker_context()->jobs(), registration_->storage_key(), registration_->scope_url(), std::move(promise_reference), environment_settings()->context())); // 5. Return promise. diff --git a/cobalt/worker/service_worker_registration_map.cc b/cobalt/worker/service_worker_registration_map.cc index 760c45720231..3532094231ad 100644 --- a/cobalt/worker/service_worker_registration_map.cc +++ b/cobalt/worker/service_worker_registration_map.cc @@ -29,7 +29,7 @@ #include "cobalt/script/exception_message.h" #include "cobalt/script/promise.h" #include "cobalt/script/script_value.h" -#include "cobalt/worker/service_worker_jobs.h" +#include "cobalt/worker/service_worker_context.h" #include "cobalt/worker/service_worker_registration_object.h" #include "cobalt/worker/service_worker_update_via_cache.h" #include "url/gurl.h" @@ -252,7 +252,7 @@ bool ServiceWorkerRegistrationMap::IsUnregistered( } void ServiceWorkerRegistrationMap::HandleUserAgentShutdown( - ServiceWorkerJobs* jobs) { + ServiceWorkerContext* context) { TRACE_EVENT0("cobalt::worker", "ServiceWorkerRegistrationMap::HandleUserAgentShutdown()"); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); @@ -270,7 +270,7 @@ void ServiceWorkerRegistrationMap::HandleUserAgentShutdown( // active worker is null, invoke Clear Registration with registration and // continue to the next iteration of the loop. if (!registration->waiting_worker() && !registration->active_worker()) { - jobs->ClearRegistration(registration); + context->ClearRegistration(registration); continue; } else { // 1.1.2. Else, set installingWorker to null. @@ -283,7 +283,7 @@ void ServiceWorkerRegistrationMap::HandleUserAgentShutdown( // substep in parallel: // 1.2.1. Invoke Activate with registration. - jobs->Activate(registration); + context->Activate(registration); } } } diff --git a/cobalt/worker/service_worker_registration_map.h b/cobalt/worker/service_worker_registration_map.h index f998319ff469..591f58fcc907 100644 --- a/cobalt/worker/service_worker_registration_map.h +++ b/cobalt/worker/service_worker_registration_map.h @@ -37,7 +37,8 @@ namespace cobalt { namespace worker { -class ServiceWorkerJobs; + +class ServiceWorkerContext; // Algorithms for the service worker scope to registration map. // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-scope-to-registration-map @@ -69,11 +70,11 @@ class ServiceWorkerRegistrationMap { bool IsUnregistered(ServiceWorkerRegistrationObject* registration); // https://www.w3.org/TR/2022/CRD-service-workers-20220712/#on-user-agent-shutdown-algorithm - void HandleUserAgentShutdown(ServiceWorkerJobs* jobs); + void HandleUserAgentShutdown(ServiceWorkerContext* context); void AbortAllActive(); - // Called from the end of ServiceWorkerJobs Install, Activate, and Clear + // Called from the end of ServiceWorkerContext Install, Activate, and Clear // Registration since these are the cases in which a service worker // registration's active_worker or waiting_worker are updated. void PersistRegistration(const url::Origin& storage_key, const GURL& scope);