blob: 5c39206487c8c33143625cbba4f08e67f55a26f4 [file] [log] [blame]
// 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 <list>
#include <utility>
#include <vector>
#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<script::ValuePromiseWrappable::Reference>
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> 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<script::ValuePromiseWrappable::Reference>
promise_reference,
scoped_refptr<Client> 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<script::ValuePromiseWrappable::Reference>
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<WindowData> 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<script::ValuePromiseWrappable::Reference>
promise_reference,
std::unique_ptr<WindowData> 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<Client> 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<ServiceWorkerJobs>(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<GURL>& maybe_scope_url,
const GURL& script_url_with_fragment,
std::unique_ptr<script::ValuePromiseWrappable::Reference> 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<char> 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<ServiceWorkerJobs::Job> 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<ServiceWorkerJobs::Job> 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_->MatchServiceWorkerRegistration(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<ServiceWorkerRegistrationObject>& 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->GetWindowOrWorkerGlobalScope()->IsServiceWorker()) continue;
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<web::Context*> matched_clients;
for (auto& context : web_context_registrations_) {
url::Origin context_storage_key =
url::Origin::Create(context->environment_settings()->creation_url());
scoped_refptr<ServiceWorkerRegistrationObject> 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:
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-use
for (const auto& client : 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 (client->active_service_worker() &&
client->active_service_worker()
->containing_service_worker_registration() == 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<ExtendableEvent> 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<ServiceWorkerObject>& 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.
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<ServiceWorkerRegistrationObject> 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<script::ValuePromiseWrappable::Reference>
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<ServiceWorkerRegistrationObject> 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<script::ValuePromiseWrappable::Reference> promise,
scoped_refptr<ServiceWorkerRegistrationObject> 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<script::ValuePromiseSequenceWrappable::Reference>
promise_reference) {
DCHECK_EQ(message_loop(), base::MessageLoop::current());
std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
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<script::ValuePromiseSequenceWrappable::Reference>
promise,
std::vector<scoped_refptr<ServiceWorkerRegistrationObject>>
registration_objects) {
TRACE_EVENT0(
"cobalt::worker",
"ServiceWorkerContext::GetRegistrationSubSteps() Resolve");
script::Sequence<scoped_refptr<script::Wrappable>> registrations;
for (auto registration_object : registration_objects) {
registrations.push_back(scoped_refptr<script::Wrappable>(
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<script::ValuePromiseVoid::Reference> 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<script::ValuePromiseVoid::Reference> 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<script::ValuePromiseWrappable::Reference> 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<script::ValuePromiseWrappable::Reference>
promise_reference) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerContext::ClientsGetSubSteps() Resolve");
promise_reference->value().Resolve(scoped_refptr<Client>());
},
std::move(promise_reference)));
}
void ServiceWorkerContext::ClientsMatchAllSubSteps(
web::Context* worker_context,
ServiceWorkerObject* associated_service_worker,
std::unique_ptr<script::ValuePromiseSequenceWrappable::Reference>
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<web::Context*> 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<std::vector<WindowData>> matched_window_data(
new std::vector<WindowData>);
// 2.4. Let matchedClients be a new list.
std::unique_ptr<std::vector<web::Context*>> matched_clients(
new std::vector<web::Context*>);
// 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<script::ValuePromiseSequenceWrappable::Reference>
promise_reference,
std::unique_ptr<std::vector<WindowData>> matched_window_data,
std::unique_ptr<std::vector<web::Context*>> matched_clients) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerContext::ClientsMatchAllSubSteps() "
"Resolve Promise");
// 2.6.1. Let clientObjects be a new list.
script::Sequence<scoped_refptr<script::Wrappable>> 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<Client> 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> 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<script::ValuePromiseVoid::Reference> 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<web::Context*> 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<ServiceWorkerRegistrationObject> 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<script::ValuePromiseVoid::Reference> promise) {
promise->value().Resolve();
},
std::move(promise_reference)));
}
void ServiceWorkerContext::ServiceWorkerPostMessageSubSteps(
ServiceWorkerObject* service_worker, web::Context* incumbent_client,
std::unique_ptr<script::StructuredClone> 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<script::StructuredClone> 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<script::StructuredClone>
structured_clone) {
ExtendableMessageEventInit init_dict;
if (incumbent_type ==
base::GetTypeId<ServiceWorkerGlobalScope>()) {
// 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<dom::Window>()) {
// . 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<ServiceWorkerRegistrationObject> 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