Skip to content

Commit

Permalink
LibWeb: Start implementing serviceWorker.register
Browse files Browse the repository at this point in the history
This is mostly the fun boilerplate. Actually creating the Job queue
to do the heavy lifting is next.
  • Loading branch information
ADKaster authored and tcl3 committed Sep 20, 2024
1 parent acd604c commit c77d9a2
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ServiceWorker registration failed: InternalError: TODO(ServiceWorkerContainer::start_register is not implemented in LibJS)
20 changes: 20 additions & 0 deletions Tests/LibWeb/Text/input/ServiceWorker/service-worker-register.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<script>
asyncTest(done => {

spoofCurrentURL("https://example.com/service-worker-register.html");

let swPromise = navigator.serviceWorker.register("service-worker.js");

swPromise
.then(registration => {
println(`ServiceWorker registration successful with scope: ${registration.scope}`);
done();
})
.catch(err => {
println(`ServiceWorker registration failed: ${err}`);
done();
});
});
</script>
2 changes: 2 additions & 0 deletions Tests/LibWeb/Text/input/ServiceWorker/service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// FIXME: Add service worker code here
console.log("hi from service worker");
123 changes: 123 additions & 0 deletions Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
#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/HTML/ServiceWorkerContainer.h>
#include <LibWeb/StorageAPI/StorageKey.h>

namespace Web::HTML {

Expand Down Expand Up @@ -40,6 +42,127 @@ JS::NonnullGCPtr<ServiceWorkerContainer> ServiceWorkerContainer::create(JS::Real
return realm.heap().allocate<ServiceWorkerContainer>(realm, realm);
}

// https://w3c.github.io/ServiceWorker/#navigator-service-worker-register
JS::NonnullGCPtr<JS::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 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<URL::URL> 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<JS::Promise>(*p->promise());
}

// https://w3c.github.io/ServiceWorker/#start-register-algorithm
void ServiceWorkerContainer::start_register(Optional<URL::URL> scope_url, URL::URL script_url, JS::NonnullGCPtr<WebIDL::Promise> 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::InternalError>(JS::ErrorType::NotImplemented, "ServiceWorkerContainer::start_register"sv).value());
}

#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void ServiceWorkerContainer::set_##attribute_name(WebIDL::CallbackType* value) \
Expand Down
14 changes: 14 additions & 0 deletions Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

#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) \
Expand All @@ -16,6 +20,12 @@

namespace Web::HTML {

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);
JS_DECLARE_ALLOCATOR(ServiceWorkerContainer);
Expand All @@ -24,6 +34,8 @@ class ServiceWorkerContainer : public DOM::EventTarget {
[[nodiscard]] static JS::NonnullGCPtr<ServiceWorkerContainer> create(JS::Realm& realm);
virtual ~ServiceWorkerContainer() override;

JS::NonnullGCPtr<JS::Promise> register_(String script_url, RegistrationOptions const& options);

#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \
void set_##attribute_name(WebIDL::CallbackType*); \
Expand All @@ -37,6 +49,8 @@ class ServiceWorkerContainer : public DOM::EventTarget {
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, JS::NonnullGCPtr<WebIDL::Promise>, EnvironmentSettingsObject&, URL::URL referrer, Bindings::WorkerType, Bindings::ServiceWorkerUpdateViaCache);

JS::NonnullGCPtr<EnvironmentSettingsObject> m_service_worker_client;
};

Expand Down
3 changes: 2 additions & 1 deletion Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.idl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ 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 = {});
// 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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import <DOM/EventTarget.idl>
#import <DOM/EventHandler.idl>
#import <HTML/ServiceWorker.idl>

// https://w3c.github.io/ServiceWorker/#serviceworkerregistration-interface
[SecureContext, Exposed=(Window,Worker)]
Expand Down

0 comments on commit c77d9a2

Please sign in to comment.