blob: 771329f2d3ad652597a64c1677bf442847ca10fb [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/worker/service_worker_jobs.h"
#include <list>
#include <map>
#include <memory>
#include <queue>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/synchronization/lock.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/base/tokens.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/worker/service_worker.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/worker_type.h"
#include "net/base/url_util.h"
#include "starboard/atomic.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace cobalt {
namespace worker {
namespace {
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);
}
bool IsOriginPotentiallyTrustworthy(const GURL& url) {
// Algorithm for potentially trustworthy origin:
// https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin
const url::Origin origin(url::Origin::Create(url));
// 1. If origin is an opaque origin, return "Not Trustworthy".
if (origin.unique()) return false;
// 2. Assert: origin is a tuple origin.
DCHECK(!origin.unique());
DCHECK(url.is_valid());
// 3. If origin’s scheme is either "https" or "wss", return "Potentially
// Trustworthy".
if (url.SchemeIsCryptographic()) return true;
// 4. If origin’s host matches one of the CIDR notations 127.0.0.0/8 or
// ::1/128 [RFC4632], return "Potentially Trustworthy".
if (net::IsLocalhost(url)) return true;
// 5. If the user agent conforms to the name resolution rules in
// [let-localhost-be-localhost] and one of the following is true:
// origin’s host is "localhost" or "localhost."
// origin’s host ends with ".localhost" or ".localhost."
// then return "Potentially Trustworthy".
// Covered by implementation of step 4.
// 6. If origin’s scheme is "file", return "Potentially Trustworthy".
if (url.SchemeIsFile()) return true;
// 7. If origin’s scheme component is one which the user agent considers to be
// authenticated, return "Potentially Trustworthy".
if (url.SchemeIs("h5vcc-embedded")) return true;
// 8. If origin has been configured as a trustworthy origin, return
// "Potentially Trustworthy".
// 9. Return "Not Trustworthy".
return false;
}
bool PermitAnyURL(const GURL&, bool) { return true; }
} // namespace
ServiceWorkerJobs::ServiceWorkerJobs(network::NetworkModule* network_module,
base::MessageLoop* message_loop)
: network_module_(network_module), message_loop_(message_loop) {
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_);
}
ServiceWorkerJobs::~ServiceWorkerJobs() {
DCHECK_EQ(message_loop(), base::MessageLoop::current());
if (!web_context_registrations_.empty()) {
DLOG(INFO) << "Waiting for web context registrations to be cleared. "
<< web_context_registrations_.size();
// Wait for web context registrations to be cleared.
web_context_registrations_cleared_.Wait();
}
}
void ServiceWorkerJobs::StartRegister(
const base::Optional<GURL>& maybe_scope_url,
const GURL& script_url_with_fragment,
std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference,
web::EnvironmentSettings* client, const WorkerType& type,
const ServiceWorkerUpdateViaCache& update_via_cache) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::StartRegister()");
DCHECK_NE(message_loop(), base::MessageLoop::current());
DCHECK_EQ(client->context()->message_loop(), base::MessageLoop::current());
// Algorithm for Start Register:
// https://w3c.github.io/ServiceWorker/#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;
}
// 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 = url::Origin::Create(client->creation_url());
// 11. Let job be the result of running Create Job with register, storage key,
// scopeURL, scriptURL, promise, and client.
std::unique_ptr<Job> 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.
message_loop()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ServiceWorkerJobs::ScheduleJob,
base::Unretained(this), std::move(job)));
DCHECK(!job.get());
}
void ServiceWorkerJobs::PromiseErrorData::Reject(
std::unique_ptr<JobPromiseType> promise) const {
if (message_type_ != script::kNoError) {
promise->Reject(GetSimpleExceptionType(message_type_));
} else {
promise->Reject(new web::DOMException(exception_code_, message_));
}
}
std::unique_ptr<ServiceWorkerJobs::Job> ServiceWorkerJobs::CreateJob(
JobType type, const url::Origin& storage_key, const GURL& scope_url,
const GURL& script_url, std::unique_ptr<JobPromiseType> promise,
web::EnvironmentSettings* client) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::CreateJob()");
// Algorithm for Create Job:
// https://w3c.github.io/ServiceWorker/#create-job
// 1. Let job be a new job.
// 2. Set job’s job type to jobType.
// 3. Set job’s storage key to storage key.
// 4. Set job’s scope url to scopeURL.
// 5. Set job’s script url to scriptURL.
// 6. Set job’s job promise to promise.
// 7. Set job’s client to client.
std::unique_ptr<Job> job(new Job(kRegister, storage_key, scope_url,
script_url, client, std::move(promise)));
// 8. If client is not null, set job’s referrer to client’s creation URL.
if (client) {
job->referrer = client->creation_url();
}
// 9. Return job.
return job;
}
void ServiceWorkerJobs::ScheduleJob(std::unique_ptr<Job> job) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ScheduleJob()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
DCHECK(job);
// Algorithm for Schedule Job:
// https://w3c.github.io/ServiceWorker/#schedule-job
// 1. Let jobQueue be null.
// 2. Let jobScope be job’s scope url, serialized.
std::string job_scope = job->scope_url.spec();
// 3. If scope to job queue map[jobScope] does not exist, set scope to job
// queue map[jobScope] to a new job queue.
if (job_queue_map_.find(job_scope) == job_queue_map_.end()) {
auto insertion = job_queue_map_.emplace(
job_scope, std::unique_ptr<JobQueue>(new JobQueue()));
DCHECK(insertion.second);
}
// 4. Set jobQueue to scope to job queue map[jobScope].
DCHECK(job_queue_map_.find(job_scope) != job_queue_map_.end());
JobQueue* job_queue = job_queue_map_.find(job_scope)->second.get();
// 5. If jobQueue is empty, then:
if (job_queue->empty()) {
// 5.1. Set job’s containing job queue to jobQueue, and enqueue job to
// jobQueue.
job->containing_job_queue = job_queue;
job_queue->Enqueue(std::move(job));
// 5.2. Invoke Run Job with jobQueue.
RunJob(job_queue);
} else {
// 6. Else:
// 6.1. Let lastJob be the element at the back of jobQueue.
{
auto last_item = job_queue->LastItem();
Job* last_job = last_item.first;
// 6.2. If job is equivalent to lastJob and lastJob’s job promise has not
// settled, append job to lastJob’s list of equivalent jobs.
DCHECK(last_job);
base::AutoLock lock(last_job->equivalent_jobs_promise_mutex);
if (EquivalentJobs(job.get(), last_job) && last_job->promise &&
last_job->promise->State() == script::PromiseState::kPending) {
last_job->equivalent_jobs.push_back(std::move(job));
return;
}
}
// 6.3. Else, set job’s containing job queue to jobQueue, and enqueue job to
// jobQueue.
job->containing_job_queue = job_queue;
job_queue->Enqueue(std::move(job));
}
DCHECK(!job);
}
bool ServiceWorkerJobs::EquivalentJobs(Job* one, Job* two) {
// Algorithm for Two jobs are equivalent:
// https://w3c.github.io/ServiceWorker/#dfn-job-equivalent
DCHECK(one);
DCHECK(two);
if (!one || !two) {
return false;
}
// Two jobs are equivalent when their job type is the same and:
if (one->type != two->type) {
return false;
}
// For register and update jobs, their scope url, script url, worker type, and
// update via cache mode are the same.
if ((one->type == kRegister || one->type == kUpdate) &&
(one->scope_url == two->scope_url) &&
(one->script_url == two->script_url) &&
(one->update_via_cache == two->update_via_cache)) {
return true;
}
// For unregister jobs, their scope url is the same.
return (one->type == kUnregister) && (one->scope_url == two->scope_url);
}
void ServiceWorkerJobs::RunJob(JobQueue* job_queue) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RunJob()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
// Algorithm for Run Job:
// https://w3c.github.io/ServiceWorker/#run-job-algorithm
// 1. Assert: jobQueue is not empty.
DCHECK(job_queue && !job_queue->empty());
if (!job_queue || job_queue->empty()) {
return;
}
// 2. Queue a task to run these steps:
message_loop()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&ServiceWorkerJobs::RunJobTask,
base::Unretained(this), job_queue));
}
void ServiceWorkerJobs::RunJobTask(JobQueue* job_queue) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RunJobTask()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
// Task for "Run Job" to run in the service worker thread.
// https://w3c.github.io/ServiceWorker/#run-job-algorithm
DCHECK(job_queue);
if (!job_queue) return;
DCHECK(!job_queue->empty());
// 2.1 Let job be the first item in jobQueue.
Job* job = job_queue->FirstItem();
DCHECK(job);
switch (job->type) {
// 2.2 If job’s job type is register, run Register with job in parallel.
case kRegister:
Register(job);
break;
// 2.3 Else if job’s job type is update, run Update with job in parallel.
case kUpdate:
Update(job);
break;
// 2.4 Else if job’s job type is unregister, run Unregister with job in
// parallel.
case kUnregister:
Unregister(job);
break;
default:
NOTREACHED();
}
}
void ServiceWorkerJobs::Register(Job* job) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Register()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
DCHECK(job);
// Algorithm for Register:
// https://w3c.github.io/ServiceWorker/#register-algorithm
// 1. If the result of running potentially trustworthy origin with the origin
// of job’s script url as the argument is Not Trusted, then:
if (!IsOriginPotentiallyTrustworthy(job->script_url)) {
// 1.1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
RejectJobPromise(
job, PromiseErrorData(
web::DOMException::kSecurityErr,
"Service Worker Register failed: Script URL is Not Trusted."));
// 1.2. Invoke Finish Job with job and abort these steps.
FinishJob(job);
return;
}
// 2. If job’s script url's origin and job’s referrer's origin are not same
// origin, then:
const url::Origin job_script_origin(url::Origin::Create(job->script_url));
const url::Origin job_referrer_origin(url::Origin::Create(job->referrer));
if (!job_script_origin.IsSameOriginWith(job_referrer_origin)) {
// 2.1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
RejectJobPromise(
job, PromiseErrorData(
web::DOMException::kSecurityErr,
"Service Worker Register failed: Script URL and referrer "
"origin are not the same."));
// 2.2. Invoke Finish Job with job and abort these steps.
FinishJob(job);
return;
}
// 3. If job’s scope url's origin and job’s referrer's origin are not same
// origin, then:
const url::Origin job_scope_origin(url::Origin::Create(job->scope_url));
if (!job_scope_origin.IsSameOriginWith(job_referrer_origin)) {
// 3.1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
RejectJobPromise(
job, PromiseErrorData(
web::DOMException::kSecurityErr,
"Service Worker Register failed: Scope URL and referrer "
"origin are not the same."));
// 3.2. Invoke Finish Job with job and abort these steps.
FinishJob(job);
return;
}
// 4. Let registration be the result of running Get Registration given job’s
// storage key and job’s scope url.
ServiceWorkerRegistrationObject* registration =
scope_to_registration_map_.GetRegistration(job->storage_key,
job->scope_url);
// 5. If registration is not null, then:
if (registration) {
// 5.1 Let newestWorker be the result of running the Get Newest Worker
// algorithm passing registration as the argument.
ServiceWorkerObject* newest_worker = registration->GetNewestWorker();
// 5.2 If newestWorker is not null, job’s script url equals newestWorker’s
// script url, job’s worker type equals newestWorker’s type, and job’s
// update via cache mode's value equals registration’s update via cache
// mode, then:
if (newest_worker && job->script_url == newest_worker->script_url()) {
// 5.2.1 Invoke Resolve Job Promise with job and registration.
ResolveJobPromise(job, registration);
// 5.2.2 Invoke Finish Job with job and abort these steps.
FinishJob(job);
return;
}
} else {
// 6. Else:
// 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(
job->storage_key, job->scope_url, job->update_via_cache);
}
// 7. Invoke Update algorithm passing job as the argument.
Update(job);
}
void ServiceWorkerJobs::Update(Job* job) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Update()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
DCHECK(job);
// Algorithm for Update:
// https://w3c.github.io/ServiceWorker/#update-algorithm
// 1. Let registration be the result of running Get Registration given job’s
// storage key and job’s scope url.
ServiceWorkerRegistrationObject* registration =
scope_to_registration_map_.GetRegistration(job->storage_key,
job->scope_url);
// 2. If registration is null, then:
if (!registration) {
// 2.1. Invoke Reject Job Promise with job and TypeError.
RejectJobPromise(job, PromiseErrorData(script::kSimpleTypeError));
// 2.2. Invoke Finish Job with job and abort these steps.
FinishJob(job);
return;
}
// 3. Let newestWorker be the result of running Get Newest Worker algorithm
// passing registration as the argument.
ServiceWorkerObject* newest_worker = registration->GetNewestWorker();
// 4. If job’s job type is update, and newestWorker is not null and its script
// url does not equal job’s script url, then:
if ((job->type == kUpdate) && newest_worker &&
(newest_worker->script_url() != job->script_url)) {
// 4.1 Invoke Reject Job Promise with job and TypeError.
RejectJobPromise(job, PromiseErrorData(script::kSimpleTypeError));
// 4.2 Invoke Finish Job with job and abort these steps.
FinishJob(job);
return;
}
auto state(
base::MakeRefCounted<UpdateJobState>(job, registration, newest_worker));
// 5. Let referrerPolicy be the empty string.
// 6. Let hasUpdatedResources be false.
state->has_updated_resources = false;
// 7. Let updatedResourceMap be an ordered map where the keys are URLs and the
// values are responses.
// That is located in job->updated_resource_map.
// 8. Switching on job’s worker type, run these substeps with the following
// options:
// - "classic"
// Fetch a classic worker script given job’s serialized script url,
// job’s client, "serviceworker", and the to-be-created environment
// settings object for this service worker.
// - "module"
// Fetch a module worker script graph given job’s serialized script
// url, job’s client, "serviceworker", "omit", and the to-be-created
// environment settings object for this service worker.
// To perform the fetch given request, run the following steps:
// 8.1. Append `Service-Worker`/`script` to request’s header list.
// 8.2. Set request’s cache mode to "no-cache" if any of the following are
// true:
// - registration’s update via cache mode is not "all".
// - job’s force bypass cache flag is set.
// - newestWorker is not null and registration is stale.
// 8.3. Set request’s service-workers mode to "none".
// 8.4. If the is top-level flag is unset, then return the result of
// fetching request.
// 8.5. Set request’s redirect mode to "error".
// 8.6. Fetch request, and asynchronously wait to run the remaining steps
// as part of fetch’s process response for the response response.
// 8.7. Extract a MIME type from the response’s header list. If this MIME
// type (ignoring parameters) is not a JavaScript MIME type, then:
// 8.7.1. Invoke Reject Job Promise with job and "SecurityError"
// DOMException.
// 8.7.2. Asynchronously complete these steps with a network error.
// 8.8. Let serviceWorkerAllowed be the result of extracting header list
// values given `Service-Worker-Allowed` and response’s header list.
// 8.9. Set policyContainer to the result of creating a policy container
// from a fetch response given response.
// 8.10. If serviceWorkerAllowed is failure, then:
// 8.10.1 Asynchronously complete these steps with a network error.
// 8.11. Let scopeURL be registration’s scope url.
// 8.12. Let maxScopeString be null.
// 8.13. If serviceWorkerAllowed is null, then:
// 8.13.1. Let resolvedScope be the result of parsing "./" using job’s
// script url as the base URL.
// 8.13.2. Set maxScopeString to "/", followed by the strings in
// resolvedScope’s path (including empty strings), separated from
// each other by "/".
// 8.14. Else:
// 8.14.1. Let maxScope be the result of parsing serviceWorkerAllowed using
// job’s script url as the base URL.
// 8.14.2. If maxScope’s origin is job’s script url's origin, then:
// 8.14.2.1. Set maxScopeString to "/", followed by the strings in
// maxScope’s path (including empty strings), separated from each
// other by "/".
// 8.15. Let scopeString be "/", followed by the strings in scopeURL’s path
// (including empty strings), separated from each other by "/".
// 8.16. If maxScopeString is null or scopeString does not start with
// maxScopeString, then:
// 8.16.1. Invoke Reject Job Promise with job and "SecurityError"
// DOMException.
// 8.16.2. Asynchronously complete these steps with a network error.
// TODO(b/225037465): Implement CSP check.
csp::SecurityCallback csp_callback = base::Bind(&PermitAnyURL);
loader::Origin origin = loader::Origin(job->script_url.GetOrigin());
job->loader = script_loader_factory_->CreateScriptLoader(
job->script_url, origin, csp_callback,
base::Bind(&ServiceWorkerJobs::UpdateOnContentProduced,
base::Unretained(this), state),
base::Bind(&ServiceWorkerJobs::UpdateOnLoadingComplete,
base::Unretained(this), state));
}
void ServiceWorkerJobs::UpdateOnContentProduced(
scoped_refptr<UpdateJobState> state, const loader::Origin& last_url_origin,
std::unique_ptr<std::string> content) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerJobs::UpdateOnContentProduced()");
// Note: There seems to be missing handling of network errors here.
// 8.17. Let url be request’s url.
// 8.18. Set updatedResourceMap[url] to response.
state->updated_resource_map.insert(
std::make_pair(state->job->script_url, std::move(content)));
// 8.19. If response’s cache state is not "local", set registration’s last
// update check time to the current time.
// 8.20. Set hasUpdatedResources to true if any of the following are true:
// - newestWorker is null.
// - newestWorker’s script url is not url or newestWorker’s type is
// not job’s worker type.
// Note: Cobalt only supports 'classic' worker type.
// - newestWorker’s script resource map[url]'s body is not
// byte-for-byte identical with response’s body.
if (state->newest_worker == nullptr) {
state->has_updated_resources = true;
} else {
if (state->newest_worker->script_url() != state->job->script_url) {
state->has_updated_resources = true;
} else {
std::string* script_resource =
state->newest_worker->LookupScriptResource(state->job->script_url);
if (script_resource && content && (*script_resource != *content)) {
state->has_updated_resources = true;
}
}
}
// TODO(b/228900516): The logic below is needed for importScripts().
// 8.21. If hasUpdatedResources is false and newestWorker’s classic
// scripts imported flag is set, then:
// 8.21.1. For each importUrl → storedResponse of newestWorker’s script
// resource map:
// 8.21.1.1. If importUrl is url, then continue.
// 8.21.1.2. Let importRequest be a new request whose url is importUrl,
// client is job’s client, destination is "script", parser
// metadata is "not parser-inserted", synchronous flag is set,
// and whose use-URL-credentials flag is set.
// 8.21.1.3. Set importRequest’s cache mode to "no-cache" if any of the
// following are true:
// - registration’s update via cache mode is "none".
// - job’s force bypass cache flag is set.
// - registration is stale.
// 8.21.1.4. Let fetchedResponse be the result of fetching importRequest.
// 8.21.1.5. Set updatedResourceMap[importRequest’s url] to
// fetchedResponse.
// 8.21.1.6. Set fetchedResponse to fetchedResponse’s unsafe response.
// 8.21.1.7. If fetchedResponse’s cache state is not
// "local", set registration’s last update check time to the
// current time.
// 8.21.1.8. If fetchedResponse is a bad import script response, continue.
// 8.21.1.9. If fetchedResponse’s body is not byte-for-byte identical with
// storedResponse’s unsafe response's body, set
// hasUpdatedResources to true.
// 8.22. Asynchronously complete these steps with response.
}
void ServiceWorkerJobs::UpdateOnLoadingComplete(
scoped_refptr<UpdateJobState> state,
const base::Optional<std::string>& error) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerJobs::UpdateOnLoadingComplete()");
// TODO: This shouldn't run until the load for each script from the script
// resource map completes (step 8.22).
// When the algorithm asynchronously completes, continue the rest of these
// steps, with script being the asynchronous completion value.
auto entry = state->updated_resource_map.find(state->job->script_url);
auto* script = entry != state->updated_resource_map.end()
? entry->second.get()
: nullptr;
// 9. If script is null or Is Async Module with script’s record, script’s
// base URL, and {} it true, then:
if (script == nullptr) {
// 9.1. Invoke Reject Job Promise with job and TypeError.
RejectJobPromise(state->job, PromiseErrorData(script::kSimpleTypeError));
// 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);
}
// 9.3. Invoke Finish Job with job and abort these steps.
FinishJob(state->job);
return;
}
// 10. If hasUpdatedResources is false, then:
if (!state->has_updated_resources) {
// 10.1. Set registration’s update via cache mode to job’s update via cache
// mode.
state->registration->set_update_via_cache_mode(
state->job->update_via_cache);
// 10.2. Invoke Resolve Job Promise with job and registration.
ResolveJobPromise(state->job, state->registration);
// 10.3. Invoke Finish Job with job and abort these steps.
FinishJob(state->job);
return;
}
// 11. Let worker be a new service worker.
ServiceWorkerObject::Options options(
"ServiceWorker", state->job->client->context()->network_module());
std::unique_ptr<ServiceWorkerObject> 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
// script resource map to updatedResourceMap.
// -> The worker's script resource is set in the resource map at step 8.18.
worker->set_script_url(state->job->script_url);
worker->set_script_resource_map(std::move(state->updated_resource_map));
// 13. Append url to worker’s set of used scripts.
// -> The script resource map contains the used scripts.
// 14. Set worker’s script resource’s policy container to policyContainer.
// 15. Let forceBypassCache be true if job’s force bypass cache flag is
// set, and false otherwise.
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);
// 17. If runResult is failure or an abrupt completion, then:
if (!run_result) {
// 17.1. Invoke Reject Job Promise with job and TypeError.
RejectJobPromise(state->job, PromiseErrorData(script::kSimpleTypeError));
// 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);
}
// 17.3. Invoke Finish Job with job.
FinishJob(state->job);
} else {
// 18. Else, invoke Install algorithm with job, worker, and registration
// as its arguments.
Install(state->job, worker.get(), state->registration);
}
}
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);
// return worker->Run(force_bypass_cache);
// Algorithm for "Run Service Worker"
// https://w3c.github.io/ServiceWorker/#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.
std::string* script = worker->LookupScriptResource();
// 6. Assert: script is not null.
DCHECK(script != nullptr);
// 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();
}
void ServiceWorkerJobs::Install(Job* job, ServiceWorkerObject* worker,
ServiceWorkerRegistrationObject* registration) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Install()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
// Algorithm for Install:
// https://w3c.github.io/ServiceWorker/#installation-algorithm
// 1. Let installFailed be false.
starboard::atomic_bool install_failed(false);
// 2. Let newestWorker be the result of running Get Newest Worker algorithm
// passing registration as its argument.
ServiceWorkerObject* newest_worker = registration->GetNewestWorker();
// 3. Set registration’s update via cache mode to job’s update via cache mode.
registration->set_update_via_cache_mode(job->update_via_cache);
// 4. Run the Update Registration State algorithm passing registration,
// "installing" and worker as the arguments.
UpdateRegistrationState(registration, kInstalling, worker);
// 5. Run the Update Worker State algorithm passing registration’s installing
// worker and "installing" as the arguments.
UpdateWorkerState(registration->installing_worker(),
kServiceWorkerStateInstalling);
// 6. Assert: job’s job promise is not null.
DCHECK(job->promise.get() != nullptr);
// 7. Invoke Resolve Job Promise with job and registration.
ResolveJobPromise(job, registration);
// 8. Let settingsObjects be all environment settings objects whose origin is
// registration’s scope url's origin.
auto registration_origin = registration->scope_url().GetOrigin();
// 9. For each settingsObject of settingsObjects...
for (auto& 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:
context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](web::Context* context,
ServiceWorkerRegistrationObject* registration) {
// 9.1. Let registrationObjects be every
// ServiceWorkerRegistration
// object in settingsObject’s realm, whose service worker
// registration is registration.
// There is at most one per web context, stored in the service
// worker registration object map of the web context.
// 9.2. For each registrationObject of registrationObjects, fire
// an
// event on registrationObject named updatefound.
auto registration_object =
context->LookupServiceWorkerRegistration(registration);
if (registration_object) {
context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<worker::ServiceWorkerRegistration>
registration_object) {
registration_object->DispatchEvent(
new web::Event(base::Tokens::updatefound()));
},
registration_object));
}
},
context, registration));
}
}
// 10. Let installingWorker be registration’s installing worker.
ServiceWorkerObject* installing_worker = registration->installing_worker();
// 11. If the result of running the Should Skip Event algorithm with
// installingWorker and "install" is false, then:
if (!ShouldSkipEvent(base::Tokens::install(), installing_worker)) {
// 11.1. Let forceBypassCache be true if job’s force bypass cache flag is
// set, and false otherwise.
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);
if (!run_result) {
// 11.2.1. Set installFailed to true.
install_failed.store(true);
// 11.3. Else:
} else {
// 11.3.1. Queue a task task on installingWorker’s event loop using the
// DOM manipulation task source to run the following steps:
installing_worker->web_agent()
->context()
->message_loop()
->task_runner()
->PostBlockingTask(
FROM_HERE,
base::Bind(
[](ServiceWorkerObject* installing_worker) {
// 11.3.1.1. Let e be the result of creating an event with
// ExtendableEvent.
// TODO: implement this as ExtendableEvent.
// 11.3.1.2. Initialize e’s type attribute to install.
// 11.3.1.3. Dispatch e at installingWorker’s global object.
installing_worker->worker_global_scope()->DispatchEvent(
new web::Event(base::Tokens::install()));
// 11.3.1.4. WaitForAsynchronousExtensions: Run the
// following substeps in parallel:
// 11.3.1.4.1. Wait until e is not active.
// 11.3.1.4.2. If e’s timed out flag is set, set
// installFailed to true.
// 11.3.1.4.3. Let p be the result of getting a promise to
// wait for all of e’s extend lifetime promises.
// 11.3.1.4.4. Upon rejection of p, set installFailed to
// true.
// If task is discarded, set installFailed to true.
},
installing_worker));
// 11.3.2. Wait for task to have executed or been discarded.
// Waiting is done inside PostBlockingTask above.
// 11.3.3. Wait for the step labeled WaitForAsynchronousExtensions to
// complete.
NOTIMPLEMENTED();
}
}
// 12. If installFailed is true, then:
if (install_failed.load()) {
// 12.1. Run the Update Worker State algorithm passing registration’s
// installing worker and "redundant" as the arguments.
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);
// 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(registration->storage_key(),
registration->scope_url());
}
// 12.4. Invoke Finish Job with job and abort these steps.
FinishJob(job);
return;
}
// Note: The logic below is for scripts added with importScript().
// 13. Let map be registration’s installing worker's script resource map.
// 14. Let usedSet be registration’s installing worker's set of used scripts.
// 15. For each url of map:
// 15.1. If usedSet does not contain url, then remove map[url].
// 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());
// 16.2. Run the Update Worker State algorithm passing registration’s
// waiting worker and "redundant" as the arguments.
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());
// 18. Run the Update Registration State algorithm passing registration,
// "installing" and null as the arguments.
UpdateRegistrationState(registration, kInstalling, nullptr);
// 19. Run the Update Worker State algorithm passing registration’s waiting
// worker and "installed" as the arguments.
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: Wait for tasks.
// 22. Invoke Try Activate with registration.
TryActivate(registration);
}
void ServiceWorkerJobs::TryActivate(
ServiceWorkerRegistrationObject* registration) {
// Algorithm for Try Activate:
// https://w3c.github.io/ServiceWorker/#try-activate-algorithm
NOTIMPLEMENTED();
}
void ServiceWorkerJobs::UpdateRegistrationState(
ServiceWorkerRegistrationObject* registration, RegistrationState target,
ServiceWorkerObject* source) {
TRACE_EVENT0("cobalt::worker",
"ServiceWorkerJobs::UpdateRegistrationState()");
DCHECK(registration);
// Algorithm for Update Registration State:
// https://w3c.github.io/ServiceWorker/#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()->PostTask(
FROM_HERE,
base::BindOnce(
[](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->GetServiceWorkerRegistration(registration);
if (registration_object) {
registration_object->set_installing(
context->GetServiceWorker(
registration->installing_worker()));
}
},
context, 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()->PostTask(
FROM_HERE,
base::BindOnce(
[](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, 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()->PostTask(
FROM_HERE,
base::BindOnce(
[](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, registration));
}
break;
}
default:
NOTREACHED();
}
}
void ServiceWorkerJobs::UpdateWorkerState(ServiceWorkerObject* worker,
ServiceWorkerState state) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::UpdateWorkerState()");
DCHECK(worker);
if (!worker) {
return;
}
// Algorithm for Update Worker State:
// https://w3c.github.io/ServiceWorker/#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 = worker->script_url().GetOrigin();
// 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()->PostTask(
FROM_HERE,
base::BindOnce(
[](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.
context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<worker::ServiceWorker> worker_obj) {
worker_obj->DispatchEvent(
new web::Event(base::Tokens::statechange()));
},
worker_obj));
}
},
context, worker, state));
}
}
}
bool ServiceWorkerJobs::ShouldSkipEvent(base::Token event_name,
ServiceWorkerObject* worker) {
// Algorithm for Should Skip Event:
// https://w3c.github.io/ServiceWorker/#should-skip-event-algorithm
NOTIMPLEMENTED();
return false;
}
void ServiceWorkerJobs::TerminateServiceWorker(ServiceWorkerObject* worker) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::TerminateServiceWorker()");
// Algorithm for Terminate Service Worker:
// https://w3c.github.io/ServiceWorker/#terminate-service-worker
NOTIMPLEMENTED();
}
void ServiceWorkerJobs::Unregister(Job* job) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::Unregister()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
// Algorithm for Unregister:
// https://w3c.github.io/ServiceWorker/#unregister-algorithm
NOTIMPLEMENTED();
}
void ServiceWorkerJobs::RejectJobPromise(Job* job,
const PromiseErrorData& error_data) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::RejectJobPromise()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
// Algorithm for Reject Job Promise:
// https://w3c.github.io/ServiceWorker/#reject-job-promise
// 1. If job’s client is not null, queue a task, on job’s client's responsible
// event loop using the DOM manipulation task source, to reject job’s job
// promise with a new exception with errorData and a user agent-defined
// message, in job’s client's Realm.
auto reject_task = [](std::unique_ptr<JobPromiseType> promise,
const PromiseErrorData& error_data) {
error_data.Reject(std::move(promise));
};
if (job->client) {
base::AutoLock lock(job->equivalent_jobs_promise_mutex);
job->client->context()->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(reject_task, std::move(job->promise), error_data));
// Ensure that the promise is cleared, so that equivalent jobs won't get
// added from this point on.
CHECK(!job->promise);
}
// 2. For each equivalentJob in job’s list of equivalent jobs:
for (auto& equivalent_job : job->equivalent_jobs) {
// Equivalent jobs should never have equivalent jobs of their own.
DCHECK(equivalent_job->equivalent_jobs.empty());
// 2.1. If equivalentJob’s client is null, continue.
if (equivalent_job->client) {
// 2.2. Queue a task, on equivalentJob’s client's responsible event loop
// using the DOM manipulation task source, to reject equivalentJob’s
// job promise with a new exception with errorData and a user
// agent-defined message, in equivalentJob’s client's Realm.
equivalent_job->client->context()
->message_loop()
->task_runner()
->PostTask(
FROM_HERE,
base::BindOnce(reject_task, std::move(equivalent_job->promise),
error_data));
// Check that the promise is cleared.
CHECK(!equivalent_job->promise);
}
}
job->equivalent_jobs.clear();
}
void ServiceWorkerJobs::ResolveJobPromise(
Job* job, ServiceWorkerRegistrationObject* value) {
TRACE_EVENT0("cobalt::worker", "ServiceWorkerJobs::ResolveJobPromise()");
DCHECK_EQ(message_loop(), base::MessageLoop::current());
DCHECK(job);
DCHECK(value);
// Algorithm for Resolve Job Promise:
// https://w3c.github.io/ServiceWorker/#resolve-job-promise-algorithm
// 1. If job’s client is not null, queue a task, on job’s client's responsible
// event loop using the DOM manipulation task source, to run the following
// substeps:
auto resolve_task = [](JobType type, web::EnvironmentSettings* client,
std::unique_ptr<JobPromiseType> promise,
ServiceWorkerRegistrationObject* value) {
// 1.1./2.2.1. Let convertedValue be null.
// 1.2./2.2.2. If job’s job type is either register or update, set
// convertedValue to the result of getting the service worker
// registration object that represents value in job’s client.
if (type == kRegister || type == kUpdate) {
scoped_refptr<cobalt::script::Wrappable> converted_value =
client->context()->GetServiceWorkerRegistration(value);
// 1.4./2.2.4. Resolve job’s job promise with convertedValue.
promise->Resolve(converted_value);
} else {
DCHECK_EQ(kUnregister, type);
// 1.3./2.2.3. Else, set convertedValue to value, in job’s client's Realm.
bool converted_value = value != nullptr;
// 1.4./2.2.4. Resolve job’s job promise with convertedValue.
promise->Resolve(converted_value);
}
};
if (job->client) {
base::AutoLock lock(job->equivalent_jobs_promise_mutex);
job->client->context()->message_loop()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(resolve_task, job->type, job->client,
std::move(job->promise), value));
// Ensure that the promise is cleared, so that equivalent jobs won't get
// added from this point on.
CHECK(!job->promise);
}
// 2. For each equivalentJob in job’s list of equivalent jobs:
for (auto& equivalent_job : job->equivalent_jobs) {
// Equivalent jobs should never have equivalent jobs of their own.
DCHECK(equivalent_job->equivalent_jobs.empty());
// 2.1. If equivalentJob’s client is null, continue to the next iteration of
// the loop.
if (equivalent_job->client) {
// 2.2. Queue a task, on equivalentJob’s client's responsible event loop
// using the DOM manipulation task source, to run the following
// substeps:
equivalent_job->client->context()
->message_loop()
->task_runner()
->PostTask(FROM_HERE,
base::BindOnce(resolve_task, equivalent_job->type,
equivalent_job->client,
std::move(equivalent_job->promise), value));
// Check that the promise is cleared.
CHECK(!equivalent_job->promise);
}
}
job->equivalent_jobs.clear();
}
// https://w3c.github.io/ServiceWorker/#finish-job-algorithm
void ServiceWorkerJobs::FinishJob(Job* job) {
DCHECK_EQ(message_loop(), base::MessageLoop::current());
// 1. Let jobQueue be job’s containing job queue.
JobQueue* job_queue = job->containing_job_queue;
// 2. Assert: the first item in jobQueue is job.
DCHECK_EQ(job, job_queue->FirstItem());
// 3. Dequeue from jobQueue.
job_queue->Dequeue();
// 4. If jobQueue is not empty, invoke Run Job with jobQueue.
if (!job_queue->empty()) {
RunJob(job_queue);
}
}
void ServiceWorkerJobs::GetRegistrationSubSteps(
const url::Origin& storage_key, const GURL& client_url,
web::EnvironmentSettings* client,
std::unique_ptr<script::ValuePromiseWrappable::Reference>
promise_reference) {
DCHECK_EQ(message_loop(), base::MessageLoop::current());
// Algorithm for Sub steps of ServiceWorkerContainer.getRegistration():
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-getRegistration
// 1. Let registration be the result of running Match Service Worker
// Registration algorithm with clientURL as its argument.
worker::ServiceWorkerRegistrationObject* registration =
scope_to_registration_map_.MatchServiceWorkerRegistration(storage_key,
client_url);
// 2. If registration is null, resolve promise with undefined and abort
// these steps.
// 3. Resolve promise with the result of getting the service worker
// registration object that represents registration in promise’s
// relevant settings object.
client->context()->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](web::EnvironmentSettings* settings,
std::unique_ptr<script::ValuePromiseWrappable::Reference> promise,
ServiceWorkerRegistrationObject* registration) {
promise->value().Resolve(
settings->context()->GetServiceWorkerRegistration(
registration));
},
client, std::move(promise_reference), registration));
}
void ServiceWorkerJobs::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(&ServiceWorkerJobs::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 ServiceWorkerJobs::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(&ServiceWorkerJobs::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);
if (web_context_registrations_.empty()) {
web_context_registrations_cleared_.Signal();
}
}
ServiceWorkerJobs::JobPromiseType::JobPromiseType(
std::unique_ptr<script::ValuePromiseBool::Reference> promise_reference)
: promise_bool_reference_(std::move(promise_reference)) {}
ServiceWorkerJobs::JobPromiseType::JobPromiseType(
std::unique_ptr<script::ValuePromiseWrappable::Reference> promise_reference)
: promise_wrappable_reference_(std::move(promise_reference)) {}
void ServiceWorkerJobs::JobPromiseType::Resolve(const bool result) {
DCHECK(promise_bool_reference_);
promise_bool_reference_->value().Resolve(result);
}
void ServiceWorkerJobs::JobPromiseType::Resolve(
const scoped_refptr<cobalt::script::Wrappable>& result) {
DCHECK(promise_wrappable_reference_);
promise_wrappable_reference_->value().Resolve(result);
}
void ServiceWorkerJobs::JobPromiseType::Reject(
script::SimpleExceptionType exception) {
if (promise_bool_reference_) {
promise_bool_reference_->value().Reject(exception);
return;
}
if (promise_wrappable_reference_) {
promise_wrappable_reference_->value().Reject(exception);
return;
}
NOTREACHED();
}
void ServiceWorkerJobs::JobPromiseType::Reject(
const scoped_refptr<script::ScriptException>& result) {
if (promise_bool_reference_) {
promise_bool_reference_->value().Reject(result);
return;
}
if (promise_wrappable_reference_) {
promise_wrappable_reference_->value().Reject(result);
return;
}
NOTREACHED();
}
script::PromiseState ServiceWorkerJobs::JobPromiseType::State() {
if (promise_bool_reference_) {
return promise_bool_reference_->value().State();
}
if (promise_wrappable_reference_) {
return promise_wrappable_reference_->value().State();
}
NOTREACHED();
return script::PromiseState::kPending;
}
} // namespace worker
} // namespace cobalt