blob: adabe15f71bb62f54cd658414a7a2735836ac43c [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.
#ifndef COBALT_WORKER_SERVICE_WORKER_JOBS_H_
#define COBALT_WORKER_SERVICE_WORKER_JOBS_H_
#include <deque>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/memory/scoped_refptr.h"
#include "base/message_loop/message_loop.h"
#include "base/optional.h"
#include "base/synchronization/lock.h"
#include "base/task/sequence_manager/moveable_auto_lock.h"
#include "cobalt/loader/fetcher_factory.h"
#include "cobalt/loader/script_loader_factory.h"
#include "cobalt/network/network_module.h"
#include "cobalt/script/exception_message.h"
#include "cobalt/script/promise.h"
#include "cobalt/script/script_value.h"
#include "cobalt/script/script_value_factory.h"
#include "cobalt/web/context.h"
#include "cobalt/web/dom_exception.h"
#include "cobalt/worker/service_worker_object.h"
#include "cobalt/worker/service_worker_registration_object.h"
#include "cobalt/worker/service_worker_update_via_cache.h"
#include "starboard/common/atomic.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace cobalt {
namespace worker {
class ServiceWorkerContext;
// Algorithms for Service Worker Jobs.
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#algorithms
class ServiceWorkerJobs {
public:
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-type
enum JobType { kRegister, kUpdate, kUnregister };
class JobQueue;
// This type handles the different promise variants used in jobs.
class JobPromiseType {
public:
// Constructors for each promise variant that can be held.
explicit JobPromiseType(
std::unique_ptr<script::ValuePromiseBool::Reference> promise_reference);
explicit JobPromiseType(
std::unique_ptr<script::ValuePromiseWrappable::Reference>
promise_reference);
template <typename PromiseReference>
static std::unique_ptr<JobPromiseType> Create(
PromiseReference promise_reference) {
return std::unique_ptr<JobPromiseType>(
new JobPromiseType(std::move(promise_reference)));
}
void Resolve(const bool result);
void Resolve(const scoped_refptr<cobalt::script::Wrappable>& result);
void Reject(script::SimpleExceptionType exception);
void Reject(web::DOMException::ExceptionCode code,
const std::string& message);
void Reject(const scoped_refptr<script::ScriptException>& result);
bool is_pending() const { return is_pending_.load(); }
private:
starboard::atomic_bool is_pending_{true};
std::unique_ptr<script::ValuePromiseBool::Reference>
promise_bool_reference_;
std::unique_ptr<script::ValuePromiseWrappable::Reference>
promise_wrappable_reference_;
};
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job
struct Job {
Job(JobType type, const url::Origin& storage_key, const GURL& scope_url,
const GURL& script_url, web::Context* client,
std::unique_ptr<JobPromiseType> promise)
: type(type),
storage_key(storage_key),
scope_url(scope_url),
script_url(script_url),
update_via_cache(
ServiceWorkerUpdateViaCache::kServiceWorkerUpdateViaCacheImports),
client(client),
promise(std::move(promise)) {}
~Job() {
client = nullptr;
containing_job_queue = nullptr;
}
// Job properties from the spec.
//
JobType type;
url::Origin storage_key;
GURL scope_url;
GURL script_url;
ServiceWorkerUpdateViaCache update_via_cache;
web::Context* client;
GURL referrer;
std::unique_ptr<JobPromiseType> promise;
JobQueue* containing_job_queue = nullptr;
std::deque<std::unique_ptr<Job>> equivalent_jobs;
bool force_bypass_cache_flag = false;
bool no_promise_okay = false;
// Custom, not in the spec.
//
// This lock is for the list of equivalent jobs. It should also be held when
// resolving the promise.
base::Lock equivalent_jobs_promise_mutex;
// The loader that is used for asynchronous loads.
std::unique_ptr<loader::Loader> loader;
};
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-queue
class JobQueue {
public:
bool empty() {
base::AutoLock lock(mutex_);
return jobs_.empty();
}
void Enqueue(std::unique_ptr<Job> job) {
base::AutoLock lock(mutex_);
jobs_.push_back(std::move(job));
}
std::unique_ptr<Job> Dequeue() {
base::AutoLock lock(mutex_);
std::unique_ptr<Job> job;
job.swap(jobs_.front());
jobs_.pop_front();
return job;
}
Job* FirstItem() {
base::AutoLock lock(mutex_);
return jobs_.empty() ? nullptr : jobs_.front().get();
}
// Also return a held autolock, to ensure the item remains a valid item in
// the queue while it's in use.
std::pair<Job*, base::sequence_manager::MoveableAutoLock> LastItem() {
base::sequence_manager::MoveableAutoLock lock(mutex_);
Job* job = jobs_.empty() ? nullptr : jobs_.back().get();
return std::pair<Job*, base::sequence_manager::MoveableAutoLock>(
job, std::move(lock));
}
// Ensure no references are kept to JS objects for a client that is about to
// be shutdown.
void PrepareForClientShutdown(web::Context* client);
// Helper method for PrepareForClientShutdown to help with recursion to
// equivalent jobs.
void PrepareJobForClientShutdown(const std::unique_ptr<Job>& job,
web::Context* client);
private:
base::Lock mutex_;
std::deque<std::unique_ptr<Job>> jobs_;
};
ServiceWorkerJobs(ServiceWorkerContext* service_worker_context,
network::NetworkModule* network_module,
base::MessageLoop* message_loop);
~ServiceWorkerJobs();
base::MessageLoop* message_loop() { return message_loop_; }
// Ensure no references are kept to JS objects for a client that is about to
// be shutdown.
void PrepareForClientShutdown(web::Context* client);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#create-job
std::unique_ptr<Job> CreateJob(
JobType type, const url::Origin& storage_key, const GURL& scope_url,
const GURL& script_url,
std::unique_ptr<script::ValuePromiseWrappable::Reference> promise,
web::Context* client) {
return CreateJob(type, storage_key, scope_url, script_url,
JobPromiseType::Create(std::move(promise)), client);
}
std::unique_ptr<Job> CreateJob(
JobType type, const url::Origin& storage_key, const GURL& scope_url,
const GURL& script_url,
std::unique_ptr<script::ValuePromiseBool::Reference> promise,
web::Context* client) {
return CreateJob(type, storage_key, scope_url, script_url,
JobPromiseType::Create(std::move(promise)), client);
}
std::unique_ptr<Job> CreateJobWithoutPromise(JobType type,
const url::Origin& storage_key,
const GURL& scope_url,
const GURL& script_url) {
auto job = CreateJob(type, storage_key, scope_url, script_url,
std::unique_ptr<JobPromiseType>(), /*client=*/nullptr);
job->no_promise_okay = true;
return job;
}
std::unique_ptr<Job> CreateJob(
JobType type, const url::Origin& storage_key, const GURL& scope_url,
const GURL& script_url, std::unique_ptr<JobPromiseType> promise = nullptr,
web::Context* client = nullptr);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#schedule-job
void ScheduleJob(std::unique_ptr<Job> job);
private:
friend class ServiceWorkerContext;
// State used for the 'Update' algorithm.
struct UpdateJobState : public base::RefCounted<UpdateJobState> {
UpdateJobState(
Job* job,
const scoped_refptr<ServiceWorkerRegistrationObject>& registration,
ServiceWorkerObject* newest_worker)
: job(job), registration(registration), newest_worker(newest_worker) {}
Job* job;
scoped_refptr<ServiceWorkerRegistrationObject> registration;
ServiceWorkerObject* newest_worker;
// Headers received with the main service worker script load.
scoped_refptr<net::HttpResponseHeaders> script_headers;
// map of content or resources for the worker.
ScriptResourceMap updated_resource_map;
// This represents hasUpdatedResources of the Update algorithm.
// True if any of the resources has changed since last cached.
bool has_updated_resources = false;
};
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-scope-to-job-queue-map
using JobQueueMap = std::map<std::string, std::unique_ptr<JobQueue>>;
// Type to hold the errorData for rejection of promises.
class PromiseErrorData {
public:
explicit PromiseErrorData(const script::MessageType& message_type)
: message_type_(message_type),
exception_code_(web::DOMException::kNone) {}
PromiseErrorData(const web::DOMException::ExceptionCode& code,
const std::string& message)
: message_type_(script::kNoError),
exception_code_(code),
message_(message) {}
void Reject(std::unique_ptr<JobPromiseType> promise) const;
private:
// Use script::MessageType because it can hold kNoError value to distinguish
// between simple exceptions and DOM exceptions.
script::MessageType message_type_;
const web::DOMException::ExceptionCode exception_code_;
const std::string message_;
};
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#dfn-job-equivalent
bool ReturnJobsAreEquivalent(Job* one, Job* two);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#run-job-algorithm
void RunJob(JobQueue* job_queue);
// Task for "Run Job" to run in the service worker thread.
void RunJobTask(JobQueue* job_queue);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#register-algorithm
void Register(Job* job);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#update-algorithm
void Update(Job* job);
void UpdateOnContentProduced(scoped_refptr<UpdateJobState> state,
const loader::Origin& last_url_origin,
std::unique_ptr<std::string> content);
bool UpdateOnResponseStarted(
scoped_refptr<UpdateJobState> state, loader::Fetcher* fetcher,
const scoped_refptr<net::HttpResponseHeaders>& headers);
void UpdateOnLoadingComplete(scoped_refptr<UpdateJobState> state,
const base::Optional<std::string>& error);
void UpdateOnRunServiceWorker(scoped_refptr<UpdateJobState> state,
scoped_refptr<ServiceWorkerObject> worker,
bool run_result);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#unregister-algorithm
void Unregister(Job* job);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#reject-job-promise
void RejectJobPromise(Job* job, const PromiseErrorData& error_data);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#resolve-job-promise-algorithm
void ResolveJobPromise(
Job* job, const scoped_refptr<ServiceWorkerRegistrationObject>& value) {
ResolveJobPromise(job, false, value);
}
void ResolveJobPromise(Job* job, bool value,
const scoped_refptr<ServiceWorkerRegistrationObject>&
registration = nullptr);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#finish-job-algorithm
void FinishJob(Job* job);
// https://www.w3.org/TR/2022/CRD-service-workers-20220712/#installation-algorithm
void Install(
Job* job, const scoped_refptr<ServiceWorkerObject>& worker,
const scoped_refptr<ServiceWorkerRegistrationObject>& registration);
ServiceWorkerContext* service_worker_context_;
// FetcherFactory that is used to create a fetcher according to URL.
std::unique_ptr<loader::FetcherFactory> fetcher_factory_;
// LoaderFactory that is used to acquire references to resources from a URL.
std::unique_ptr<loader::ScriptLoaderFactory> script_loader_factory_;
base::MessageLoop* message_loop_;
JobQueueMap job_queue_map_;
};
} // namespace worker
} // namespace cobalt
#endif // COBALT_WORKER_SERVICE_WORKER_JOBS_H_