LibWeb: Move ServiceWorker classes to ServiceWorker namespace

These are defined by the ServiceWorker spec, not the HTML one.
This commit is contained in:
Shannon Booth 2024-11-30 18:01:40 +13:00 committed by Andreas Kling
commit 4417f63ca0
Notes: github-actions[bot] 2024-11-30 10:20:43 +00:00
19 changed files with 44 additions and 37 deletions

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGC/Heap.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/ServiceWorker/ServiceWorker.h>
namespace Web::ServiceWorker {
ServiceWorker::ServiceWorker(JS::Realm& realm, String script_url)
: DOM::EventTarget(realm)
, m_script_url(move(script_url))
{
}
ServiceWorker::~ServiceWorker() = default;
GC::Ref<ServiceWorker> ServiceWorker::create(JS::Realm& realm)
{
return realm.create<ServiceWorker>(realm, ""_string);
}
void ServiceWorker::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ServiceWorker);
}
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void ServiceWorker::set_##attribute_name(WebIDL::CallbackType* value) \
{ \
set_event_handler_attribute(event_name, value); \
} \
WebIDL::CallbackType* ServiceWorker::attribute_name() \
{ \
return event_handler_attribute(event_name); \
}
ENUMERATE_SERVICE_WORKER_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Bindings/ServiceWorkerPrototype.h>
#include <LibWeb/DOM/EventTarget.h>
#define ENUMERATE_SERVICE_WORKER_EVENT_HANDLERS(E) \
E(onstatechange, HTML::EventNames::statechange) \
E(onerror, HTML::EventNames::error)
namespace Web::ServiceWorker {
class ServiceWorker : public DOM::EventTarget {
WEB_PLATFORM_OBJECT(ServiceWorker, DOM::EventTarget);
public:
[[nodiscard]] static GC::Ref<ServiceWorker> create(JS::Realm& realm);
virtual ~ServiceWorker() override;
String script_url() const { return m_script_url; }
Bindings::ServiceWorkerState service_worker_state() const { return m_state; }
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(WebIDL::CallbackType*); \
WebIDL::CallbackType* attribute_name();
ENUMERATE_SERVICE_WORKER_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
private:
ServiceWorker(JS::Realm&, String script_url);
virtual void initialize(JS::Realm&) override;
String m_script_url;
Bindings::ServiceWorkerState m_state { Bindings::ServiceWorkerState::Parsed };
};
}

View file

@ -0,0 +1,28 @@
#import <DOM/EventTarget.idl>
#import <DOM/EventHandler.idl>
#import <HTML/AbstractWorker.idl>
#import <HTML/MessagePort.idl>
// https://w3c.github.io/ServiceWorker/#serviceworker-interface
[SecureContext, Exposed=(Window,Worker)]
interface ServiceWorker : EventTarget {
readonly attribute USVString scriptURL;
[ImplementedAs=service_worker_state] readonly attribute ServiceWorkerState state;
[FIXME] undefined postMessage(any message, sequence<object> transfer);
[FIXME] undefined postMessage(any message, optional StructuredSerializeOptions options = {});
// event
attribute EventHandler onstatechange;
};
ServiceWorker includes AbstractWorker;
enum ServiceWorkerState {
"parsed",
"installing",
"installed",
"activating",
"activated",
"redundant"
};

View file

@ -0,0 +1,187 @@
/*
* Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ServiceWorkerContainerPrototype.h>
#include <LibWeb/DOMURL/DOMURL.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/ServiceWorker/Job.h>
#include <LibWeb/ServiceWorker/ServiceWorker.h>
#include <LibWeb/ServiceWorker/ServiceWorkerContainer.h>
#include <LibWeb/StorageAPI/StorageKey.h>
namespace Web::ServiceWorker {
GC_DEFINE_ALLOCATOR(ServiceWorkerContainer);
ServiceWorkerContainer::ServiceWorkerContainer(JS::Realm& realm)
: DOM::EventTarget(realm)
, m_service_worker_client(HTML::relevant_settings_object(*this))
{
}
ServiceWorkerContainer::~ServiceWorkerContainer() = default;
void ServiceWorkerContainer::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ServiceWorkerContainer);
}
void ServiceWorkerContainer::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_service_worker_client);
}
GC::Ref<ServiceWorkerContainer> ServiceWorkerContainer::create(JS::Realm& realm)
{
return realm.create<ServiceWorkerContainer>(realm);
}
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-register
GC::Ref<WebIDL::Promise> ServiceWorkerContainer::register_(String script_url, RegistrationOptions const& options)
{
auto& realm = this->realm();
// Note: The register(scriptURL, options) method creates or updates a service worker registration for the given scope url.
// If successful, a service worker registration ties the provided scriptURL to a scope url,
// which is subsequently used for navigation matching.
// 1. Let p be a promise.
auto p = WebIDL::create_promise(realm);
// FIXME: 2. Set scriptURL to the result of invoking Get Trusted Type compliant string with TrustedScriptURL,
// this's relevant global object, scriptURL, "ServiceWorkerContainer register", and "script".
// 3 Let client be this's service worker client.
auto client = m_service_worker_client;
// 4. Let scriptURL be the result of parsing scriptURL with this's relevant settings objects API base URL.
auto base_url = HTML::relevant_settings_object(*this).api_base_url();
auto parsed_script_url = DOMURL::parse(script_url, base_url);
// 5. Let scopeURL be null.
Optional<URL::URL> scope_url;
// 6. If options["scope"] exists, set scopeURL to the result of parsing options["scope"] with this's relevant settings objects API base URL.
if (options.scope.has_value()) {
scope_url = DOMURL::parse(options.scope.value(), base_url);
}
// 7. Invoke Start Register with scopeURL, scriptURL, p, client, clients creation URL, options["type"], and options["updateViaCache"].
start_register(scope_url, parsed_script_url, p, client, client->creation_url, options.type, options.update_via_cache);
// 8. Return p.
return p;
}
// https://w3c.github.io/ServiceWorker/#start-register-algorithm
void ServiceWorkerContainer::start_register(Optional<URL::URL> scope_url, URL::URL script_url, GC::Ref<WebIDL::Promise> promise, HTML::EnvironmentSettingsObject& client, URL::URL referrer, Bindings::WorkerType worker_type, Bindings::ServiceWorkerUpdateViaCache update_via_cache)
{
auto& realm = this->realm();
auto& vm = realm.vm();
// 1. If scriptURL is failure, reject promise with a TypeError and abort these steps.
if (!script_url.is_valid()) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL is not a valid URL"sv));
return;
}
// 2. Set scriptURLs fragment to null.
// Note: The user agent does not store the fragment of the scripts url.
// This means that the fragment does not have an effect on identifying service workers.
script_url.set_fragment({});
// 3. If scriptURLs scheme is not one of "http" and "https", reject promise with a TypeError and abort these steps.
if (!script_url.scheme().is_one_of("http"sv, "https"sv)) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL must have a scheme of 'http' or 'https'"sv));
return;
}
// 4. If any of the strings in scriptURLs path contains either ASCII case-insensitive "%2f" or ASCII case-insensitive "%5c",
// reject promise with a TypeError and abort these steps.
auto invalid_path = script_url.paths().first_matching([&](auto& path) {
return path.contains("%2f"sv, CaseSensitivity::CaseInsensitive) || path.contains("%5c"sv, CaseSensitivity::CaseInsensitive);
});
if (invalid_path.has_value()) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL path must not contain '%2f' or '%5c'"sv));
return;
}
// 5. If scopeURL is null, set scopeURL to the result of parsing the string "./" with scriptURL.
// Note: The scope url for the registration is set to the location of the service worker script by default.
if (!scope_url.has_value()) {
scope_url = DOMURL::parse("./"sv, script_url);
}
// 6. If scopeURL is failure, reject promise with a TypeError and abort these steps.
if (!scope_url->is_valid()) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL is not a valid URL"sv));
return;
}
// 7. Set scopeURLs fragment to null.
// Note: The user agent does not store the fragment of the scope url.
// This means that the fragment does not have an effect on identifying service worker registrations.
scope_url->set_fragment({});
// 8. If scopeURLs scheme is not one of "http" and "https", reject promise with a TypeError and abort these steps.
if (!scope_url->scheme().is_one_of("http"sv, "https"sv)) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL must have a scheme of 'http' or 'https'"sv));
return;
}
// 9. If any of the strings in scopeURLs path contains either ASCII case-insensitive "%2f" or ASCII case-insensitive "%5c",
// reject promise with a TypeError and abort these steps.
invalid_path = scope_url->paths().first_matching([&](auto& path) {
return path.contains("%2f"sv, CaseSensitivity::CaseInsensitive) || path.contains("%5c"sv, CaseSensitivity::CaseInsensitive);
});
if (invalid_path.has_value()) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL path must not contain '%2f' or '%5c'"sv));
return;
}
// 10. Let storage key be the result of running obtain a storage key given client.
auto storage_key = StorageAPI::obtain_a_storage_key(client);
// FIXME: Ad-Hoc. Spec should handle this failure here, or earlier.
if (!storage_key.has_value()) {
WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "Failed to obtain a storage key"sv));
return;
}
// 11. Let job be the result of running Create Job with register, storage key, scopeURL, scriptURL, promise, and client.
auto job = Job::create(vm, Job::Type::Register, storage_key.value(), scope_url.value(), script_url, promise, client);
// 12. Set jobs worker type to workerType.
job->worker_type = worker_type;
// 13. Set jobs update via cache to updateViaCache.
job->update_via_cache = update_via_cache;
// 14. Set jobs referrer to referrer.
job->referrer = move(referrer);
// 15. Invoke Schedule Job with job.
schedule_job(vm, job);
}
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void ServiceWorkerContainer::set_##attribute_name(WebIDL::CallbackType* value) \
{ \
set_event_handler_attribute(event_name, move(value)); \
} \
WebIDL::CallbackType* ServiceWorkerContainer::attribute_name() \
{ \
return event_handler_attribute(event_name); \
}
ENUMERATE_SERVICE_WORKER_CONTAINER_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
* Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/Bindings/ServiceWorkerRegistrationPrototype.h>
#include <LibWeb/Bindings/WorkerPrototype.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Promise.h>
#define ENUMERATE_SERVICE_WORKER_CONTAINER_EVENT_HANDLERS(E) \
E(oncontrollerchange, HTML::EventNames::controllerchange) \
E(onmessage, HTML::EventNames::message) \
E(onmessageerror, HTML::EventNames::messageerror)
namespace Web::ServiceWorker {
struct RegistrationOptions {
Optional<String> scope;
Bindings::WorkerType type = Bindings::WorkerType::Classic;
Bindings::ServiceWorkerUpdateViaCache update_via_cache = Bindings::ServiceWorkerUpdateViaCache::Imports;
};
class ServiceWorkerContainer : public DOM::EventTarget {
WEB_PLATFORM_OBJECT(ServiceWorkerContainer, DOM::EventTarget);
GC_DECLARE_ALLOCATOR(ServiceWorkerContainer);
public:
[[nodiscard]] static GC::Ref<ServiceWorkerContainer> create(JS::Realm& realm);
virtual ~ServiceWorkerContainer() override;
GC::Ref<WebIDL::Promise> register_(String script_url, RegistrationOptions const& options);
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(WebIDL::CallbackType*); \
WebIDL::CallbackType* attribute_name();
ENUMERATE_SERVICE_WORKER_CONTAINER_EVENT_HANDLERS(__ENUMERATE)
#undef __ENUMERATE
private:
explicit ServiceWorkerContainer(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
void start_register(Optional<URL::URL> scope_url, URL::URL script_url, GC::Ref<WebIDL::Promise>, HTML::EnvironmentSettingsObject&, URL::URL referrer, Bindings::WorkerType, Bindings::ServiceWorkerUpdateViaCache);
GC::Ref<HTML::EnvironmentSettingsObject> m_service_worker_client;
};
}

View file

@ -0,0 +1,30 @@
#import <DOM/EventTarget.idl>
#import <DOM/EventHandler.idl>
#import <HTML/Worker.idl>
#import <ServiceWorker/ServiceWorkerRegistration.idl>
// https://w3c.github.io/ServiceWorker/#serviceworkercontainer-interface
[SecureContext, Exposed=(Window,Worker)]
interface ServiceWorkerContainer : EventTarget {
[FIXME] readonly attribute ServiceWorker? controller;
[FIXME] readonly attribute Promise<ServiceWorkerRegistration> ready;
// FIXME: [NewObject] Promise<ServiceWorkerRegistration> register((TrustedScriptURL or USVString) scriptURL, optional RegistrationOptions options = {});
[NewObject, ImplementedAs=register_] Promise<ServiceWorkerRegistration> register(USVString scriptURL, optional RegistrationOptions options = {});
[FIXME, NewObject] Promise<(ServiceWorkerRegistration or undefined)> getRegistration(optional USVString clientURL = "");
[FIXME, NewObject] Promise<FrozenArray<ServiceWorkerRegistration>> getRegistrations();
[FIXME] undefined startMessages();
// events
attribute EventHandler oncontrollerchange;
attribute EventHandler onmessage; // event.source of message events is ServiceWorker object
attribute EventHandler onmessageerror;
};
dictionary RegistrationOptions {
USVString scope;
WorkerType type = "classic";
ServiceWorkerUpdateViaCache updateViaCache = "imports";
};

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ServiceWorkerRegistrationPrototype.h>
#include <LibWeb/ServiceWorker/ServiceWorkerRegistration.h>
namespace Web::ServiceWorker {
GC_DEFINE_ALLOCATOR(ServiceWorkerRegistration);
ServiceWorkerRegistration::ServiceWorkerRegistration(JS::Realm& realm)
: DOM::EventTarget(realm)
{
}
void ServiceWorkerRegistration::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(ServiceWorkerRegistration);
}
GC::Ref<ServiceWorkerRegistration> ServiceWorkerRegistration::create(JS::Realm& realm)
{
return realm.create<ServiceWorkerRegistration>(realm);
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/DOM/EventTarget.h>
namespace Web::ServiceWorker {
class ServiceWorkerRegistration : public DOM::EventTarget {
WEB_PLATFORM_OBJECT(ServiceWorkerRegistration, DOM::EventTarget);
GC_DECLARE_ALLOCATOR(ServiceWorkerRegistration);
public:
[[nodiscard]] static GC::Ref<ServiceWorkerRegistration> create(JS::Realm& realm);
explicit ServiceWorkerRegistration(JS::Realm&);
virtual ~ServiceWorkerRegistration() override = default;
virtual void initialize(JS::Realm&) override;
};
}

View file

@ -0,0 +1,27 @@
#import <DOM/EventTarget.idl>
#import <DOM/EventHandler.idl>
#import <ServiceWorker/ServiceWorker.idl>
// https://w3c.github.io/ServiceWorker/#serviceworkerregistration-interface
[SecureContext, Exposed=(Window,Worker)]
interface ServiceWorkerRegistration : EventTarget {
[FIXME] readonly attribute ServiceWorker? installing;
[FIXME] readonly attribute ServiceWorker? waiting;
[FIXME] readonly attribute ServiceWorker? active;
[FIXME, SameObject] readonly attribute NavigationPreloadManager navigationPreload;
[FIXME] readonly attribute USVString scope;
[FIXME] readonly attribute ServiceWorkerUpdateViaCache updateViaCache;
[FIXME, NewObject] Promise<undefined> update();
[FIXME, NewObject] Promise<boolean> unregister();
// event
[FIXME] attribute EventHandler onupdatefound;
};
enum ServiceWorkerUpdateViaCache {
"imports",
"all",
"none"
};