diff --git a/Tests/LibWeb/Text/expected/ServiceWorker/service-worker-register.txt b/Tests/LibWeb/Text/expected/ServiceWorker/service-worker-register.txt new file mode 100644 index 00000000000..ad8ded6ef83 --- /dev/null +++ b/Tests/LibWeb/Text/expected/ServiceWorker/service-worker-register.txt @@ -0,0 +1 @@ +ServiceWorker registration failed: InternalError: TODO(ServiceWorkerContainer::start_register is not implemented in LibJS) diff --git a/Tests/LibWeb/Text/input/ServiceWorker/service-worker-register.html b/Tests/LibWeb/Text/input/ServiceWorker/service-worker-register.html new file mode 100644 index 00000000000..d0e5b0933fa --- /dev/null +++ b/Tests/LibWeb/Text/input/ServiceWorker/service-worker-register.html @@ -0,0 +1,20 @@ + + + diff --git a/Tests/LibWeb/Text/input/ServiceWorker/service-worker.js b/Tests/LibWeb/Text/input/ServiceWorker/service-worker.js new file mode 100644 index 00000000000..e6733d97337 --- /dev/null +++ b/Tests/LibWeb/Text/input/ServiceWorker/service-worker.js @@ -0,0 +1,2 @@ +// FIXME: Add service worker code here +console.log("hi from service worker"); diff --git a/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.cpp b/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.cpp index 7d69059aad7..7a1ff4df393 100644 --- a/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.cpp +++ b/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.cpp @@ -8,8 +8,10 @@ #include #include #include +#include #include #include +#include namespace Web::HTML { @@ -40,6 +42,127 @@ JS::NonnullGCPtr ServiceWorkerContainer::create(JS::Real return realm.heap().allocate(realm, realm); } +// https://w3c.github.io/ServiceWorker/#navigator-service-worker-register +JS::NonnullGCPtr 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 object’s API base URL. + auto base_url = relevant_settings_object(*this).api_base_url(); + auto parsed_script_url = DOMURL::parse(script_url, base_url); + + // 5. Let scopeURL be null. + Optional scope_url; + + // 6. If options["scope"] exists, set scopeURL to the result of parsing options["scope"] with this's relevant settings object’s 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, client’s 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 verify_cast(*p->promise()); +} + +// https://w3c.github.io/ServiceWorker/#start-register-algorithm +void ServiceWorkerContainer::start_register(Optional scope_url, URL::URL script_url, JS::NonnullGCPtr promise, EnvironmentSettingsObject& client, URL::URL, Bindings::WorkerType, Bindings::ServiceWorkerUpdateViaCache) +{ + 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 scriptURL’s fragment to null. + // Note: The user agent does not store the fragment of the script’s url. + // This means that the fragment does not have an effect on identifying service workers. + script_url.set_fragment({}); + + // 3. If scriptURL’s 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 scriptURL’s 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 scopeURL’s 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 scopeURL’s 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 scopeURL’s 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; + } + + // FIXME: Schedule the job + // 11. Let job be the result of running Create Job with register, storage key, scopeURL, scriptURL, promise, and client. + // 12. Set job’s worker type to workerType. + // 13. Set job’s update via cache to updateViaCache. + // 14. Set job’s referrer to referrer. + // 15. Invoke Schedule Job with job. + + WebIDL::reject_promise(realm, promise, *vm.throw_completion(JS::ErrorType::NotImplemented, "ServiceWorkerContainer::start_register"sv).value()); +} + #undef __ENUMERATE #define __ENUMERATE(attribute_name, event_name) \ void ServiceWorkerContainer::set_##attribute_name(WebIDL::CallbackType* value) \ diff --git a/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.h b/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.h index 9576ac013be..18dc26ed418 100644 --- a/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.h +++ b/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.h @@ -7,7 +7,11 @@ #pragma once +#include +#include #include +#include +#include #define ENUMERATE_SERVICE_WORKER_CONTAINER_EVENT_HANDLERS(E) \ E(oncontrollerchange, HTML::EventNames::controllerchange) \ @@ -16,6 +20,12 @@ namespace Web::HTML { +struct RegistrationOptions { + Optional 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); JS_DECLARE_ALLOCATOR(ServiceWorkerContainer); @@ -24,6 +34,8 @@ public: [[nodiscard]] static JS::NonnullGCPtr create(JS::Realm& realm); virtual ~ServiceWorkerContainer() override; + JS::NonnullGCPtr register_(String script_url, RegistrationOptions const& options); + #undef __ENUMERATE #define __ENUMERATE(attribute_name, event_name) \ void set_##attribute_name(WebIDL::CallbackType*); \ @@ -37,6 +49,8 @@ private: virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; + void start_register(Optional scope_url, URL::URL script_url, JS::NonnullGCPtr, EnvironmentSettingsObject&, URL::URL referrer, Bindings::WorkerType, Bindings::ServiceWorkerUpdateViaCache); + JS::NonnullGCPtr m_service_worker_client; }; diff --git a/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.idl b/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.idl index 3884eb7d987..c8567dc23f3 100644 --- a/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.idl +++ b/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.idl @@ -9,7 +9,8 @@ interface ServiceWorkerContainer : EventTarget { [FIXME] readonly attribute ServiceWorker? controller; [FIXME] readonly attribute Promise ready; - [FIXME, NewObject] Promise register((TrustedScriptURL or USVString) scriptURL, optional RegistrationOptions options = {}); + // FIXME: [NewObject] Promise register((TrustedScriptURL or USVString) scriptURL, optional RegistrationOptions options = {}); + [NewObject, ImplementedAs=register_] Promise register(USVString scriptURL, optional RegistrationOptions options = {}); [FIXME, NewObject] Promise<(ServiceWorkerRegistration or undefined)> getRegistration(optional USVString clientURL = ""); [FIXME, NewObject] Promise> getRegistrations(); diff --git a/Userland/Libraries/LibWeb/HTML/ServiceWorkerRegistration.idl b/Userland/Libraries/LibWeb/HTML/ServiceWorkerRegistration.idl index 59c3e2e756e..9f097e9f3e8 100644 --- a/Userland/Libraries/LibWeb/HTML/ServiceWorkerRegistration.idl +++ b/Userland/Libraries/LibWeb/HTML/ServiceWorkerRegistration.idl @@ -1,5 +1,6 @@ #import #import +#import // https://w3c.github.io/ServiceWorker/#serviceworkerregistration-interface [SecureContext, Exposed=(Window,Worker)]