Browse Source

LibWeb: Implement most of Service Worker registration

This approach will need some rework to be properly handled at the user
agent level instead of per renderer process, but it's a start.
Andrew Kaster 9 months ago
parent
commit
7faebb2702

+ 5 - 1
Meta/gn/secondary/Userland/Libraries/LibWeb/ServiceWorker/BUILD.gn

@@ -1,5 +1,9 @@
 source_set("ServiceWorker") {
   configs += [ "//Userland/Libraries/LibWeb:configs" ]
   deps = [ "//Userland/Libraries/LibWeb:all_generated" ]
-  sources = [ "Job.cpp" ]
+  sources = [
+    "Job.cpp",
+    "Registration.cpp",
+    "ServiceWorker.cpp",
+  ]
 }

+ 1 - 1
Tests/LibWeb/Text/expected/ServiceWorker/service-worker-register.txt

@@ -1 +1 @@
-ServiceWorker registration failed: InternalError: TODO(Service Worker registration is not implemented in LibJS)
+ServiceWorker registration failed: InternalError: TODO(Service Worker update is not implemented in LibJS)

+ 2 - 0
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -635,6 +635,8 @@ set(SOURCES
     ResizeObserver/ResizeObserverSize.cpp
     SecureContexts/AbstractOperations.cpp
     ServiceWorker/Job.cpp
+    ServiceWorker/Registration.cpp
+    ServiceWorker/ServiceWorker.cpp
     SRI/SRI.cpp
     StorageAPI/NavigatorStorage.cpp
     StorageAPI/StorageKey.cpp

+ 169 - 9
Userland/Libraries/LibWeb/ServiceWorker/Job.cpp

@@ -1,21 +1,33 @@
 /*
- * Copyright (c) 2024, Andrew Kaster <akaster@ladybird.org>
+ * Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
 #include <LibJS/Heap/Heap.h>
 #include <LibJS/Runtime/VM.h>
+#include <LibURL/URL.h>
 #include <LibWeb/HTML/Scripting/Environments.h>
 #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
+#include <LibWeb/SecureContexts/AbstractOperations.h>
 #include <LibWeb/ServiceWorker/Job.h>
+#include <LibWeb/ServiceWorker/Registration.h>
 #include <LibWeb/WebIDL/Promise.h>
 
 namespace Web::ServiceWorker {
 
+static void run_job(JS::VM&, JobQueue&);
+static void finish_job(JS::VM&, JS::NonnullGCPtr<Job>);
+static void resolve_job_promise(JS::NonnullGCPtr<Job>, Optional<Registration const&>, JS::Value = JS::js_null());
+template<typename Error>
+static void reject_job_promise(JS::NonnullGCPtr<Job>, FlyString message);
+static void register_(JS::VM&, JS::NonnullGCPtr<Job>);
+static void update(JS::VM&, JS::NonnullGCPtr<Job>);
+static void unregister(JS::VM&, JS::NonnullGCPtr<Job>);
+
 JS_DEFINE_ALLOCATOR(Job);
 
-// https://w3c.github.io/ServiceWorker/#create-job
+// https://w3c.github.io/ServiceWorker/#create-job-algorithm
 JS::NonnullGCPtr<Job> Job::create(JS::VM& vm, Job::Type type, StorageAPI::StorageKey storage_key, URL::URL scope_url, URL::URL script_url, JS::GCPtr<WebIDL::Promise> promise, JS::GCPtr<HTML::EnvironmentSettingsObject> client)
 {
     return vm.heap().allocate_without_realm<Job>(type, move(storage_key), move(scope_url), move(script_url), promise, client);
@@ -54,14 +66,73 @@ static HashMap<ByteString, JobQueue>& scope_to_job_queue_map()
     return map;
 }
 
+// https://w3c.github.io/ServiceWorker/#register-algorithm
 static void register_(JS::VM& vm, JS::NonnullGCPtr<Job> job)
 {
-    // If there's no client, there won't be any promises to resolve
-    if (job->client) {
-        auto context = HTML::TemporaryExecutionContext(*job->client, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
-        auto& realm = *vm.current_realm();
-        WebIDL::reject_promise(realm, *job->job_promise, *vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "Service Worker registration"sv).value());
+    auto script_origin = job->script_url.origin();
+    auto scope_origin = job->scope_url.origin();
+    auto referrer_origin = job->referrer->origin();
+
+    // 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 (SecureContexts::Trustworthiness::NotTrustworthy == SecureContexts::is_origin_potentially_trustworthy(script_origin)) {
+        // 1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
+        reject_job_promise<WebIDL::SecurityError>(job, "Service Worker registration has untrustworthy script origin"_fly_string);
+
+        // 2. Invoke Finish Job with job and abort these steps.
+        finish_job(vm, job);
+        return;
+    }
+
+    // 2. If job’s script url's origin and job’s referrer's origin are not same origin, then:
+    if (!script_origin.is_same_origin(referrer_origin)) {
+        // 1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
+        reject_job_promise<WebIDL::SecurityError>(job, "Service Worker registration has incompatible script and referrer origins"_fly_string);
+
+        // 2. Invoke Finish Job with job and abort these steps.
+        finish_job(vm, job);
+        return;
     }
+
+    // 3. If job’s scope url's origin and job’s referrer's origin are not same origin, then:
+    if (!scope_origin.is_same_origin(referrer_origin)) {
+        // 1. Invoke Reject Job Promise with job and "SecurityError" DOMException.
+        reject_job_promise<WebIDL::SecurityError>(job, "Service Worker registration has incompatible scope and referrer origins"_fly_string);
+
+        // 2. Invoke Finish Job with job and abort these steps.
+        finish_job(vm, job);
+        return;
+    }
+
+    // 4. Let registration be the result of running Get Registration given job’s storage key and job’s scope url.
+    auto registration = Registration::get(job->storage_key, job->scope_url);
+
+    // 5. If registration is not null, then:
+    if (registration.has_value()) {
+        // 1. Let newestWorker be the result of running the Get Newest Worker algorithm passing registration as the argument.
+        auto* newest_worker = registration->newest_worker();
+
+        // 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 != nullptr
+            && job->script_url == newest_worker->script_url
+            && job->worker_type == newest_worker->worker_type
+            && job->update_via_cache == registration->update_via_cache()) {
+            // 1. Invoke Resolve Job Promise with job and registration.
+            resolve_job_promise(job, registration.value());
+
+            // 2. Invoke Finish Job with job and abort these steps.
+            finish_job(vm, job);
+            return;
+        }
+    }
+    // 6. Else:
+    else {
+        // 1. Invoke Set Registration algorithm with job’s storage key, job’s scope url, and job’s update via cache mode.
+        Registration::set(job->storage_key, job->scope_url, job->update_via_cache);
+    }
+
+    // Invoke Update algorithm passing job as the argument.
+    update(vm, job);
 }
 
 static void update(JS::VM& vm, JS::NonnullGCPtr<Job> job)
@@ -84,7 +155,7 @@ static void unregister(JS::VM& vm, JS::NonnullGCPtr<Job> job)
     }
 }
 
-// https://w3c.github.io/ServiceWorker/#run-job
+// https://w3c.github.io/ServiceWorker/#run-job-algorithm
 static void run_job(JS::VM& vm, JobQueue& job_queue)
 {
     // 1. Assert: jobQueue is not empty.
@@ -122,13 +193,102 @@ static void run_job(JS::VM& vm, JobQueue& job_queue)
     HTML::queue_a_task(HTML::Task::Source::Unspecified, nullptr, nullptr, job_run_steps);
 }
 
-// https://w3c.github.io/ServiceWorker/#schedule-job
+// https://w3c.github.io/ServiceWorker/#finish-job-algorithm
+static void finish_job(JS::VM& vm, JS::NonnullGCPtr<Job> job)
+{
+    // 1. Let jobQueue be job’s containing job queue.
+    auto& job_queue = *job->containing_job_queue;
+
+    // 2. Assert: the first item in jobQueue is job.
+    VERIFY(job_queue.first() == job);
+
+    // 3. Dequeue from jobQueue
+    (void)job_queue.take_first();
+
+    // 4. If jobQueue is not empty, invoke Run Job with jobQueue.
+    if (!job_queue.is_empty())
+        run_job(vm, job_queue);
+}
+
+// https://w3c.github.io/ServiceWorker/#resolve-job-promise-algorithm
+static void resolve_job_promise(JS::NonnullGCPtr<Job> job, Optional<Registration const&>, JS::Value value)
+{
+    // 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:
+    if (job->client) {
+        auto& realm = job->client->realm();
+        HTML::queue_a_task(HTML::Task::Source::DOMManipulation, job->client->responsible_event_loop(), nullptr, JS::create_heap_function(realm.heap(), [&realm, job, value] {
+            HTML::TemporaryExecutionContext const context(*job->client, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
+            // FIXME: Resolve to a ServiceWorkerRegistration platform object
+            // 1. Let convertedValue be null.
+            // 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.
+            // 3. Else, set convertedValue to value, in job’s client's Realm.
+            // 4. Resolve job’s job promise with convertedValue.
+            WebIDL::resolve_promise(realm, *job->job_promise, value);
+        }));
+    }
+
+    // 2. For each equivalentJob in job’s list of equivalent jobs:
+    for (auto& equivalent_job : job->list_of_equivalent_jobs) {
+        // 1. If equivalentJob’s client is null, continue.
+        if (!equivalent_job->client)
+            continue;
+
+        // 2. Queue a task, on equivalentJob’s client's responsible event loop using the DOM manipulation task source,
+        //    to run the following substeps:
+        auto& realm = equivalent_job->client->realm();
+        HTML::queue_a_task(HTML::Task::Source::DOMManipulation, equivalent_job->client->responsible_event_loop(), nullptr, JS::create_heap_function(realm.heap(), [&realm, equivalent_job, value] {
+            HTML::TemporaryExecutionContext const context(*equivalent_job->client, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
+            // FIXME: Resolve to a ServiceWorkerRegistration platform object
+            // 1. Let convertedValue be null.
+            // 2. If equivalentJob’s job type is either register or update, set convertedValue to the result of
+            //    getting the service worker registration object that represents value in equivalentJob’s client.
+            // 3. Else, set convertedValue to value, in equivalentJob’s client's Realm.
+            // 4. Resolve equivalentJob’s job promise with convertedValue.
+            WebIDL::resolve_promise(realm, *equivalent_job->job_promise, value);
+        }));
+    }
+}
+
+// https://w3c.github.io/ServiceWorker/#reject-job-promise-algorithm
+template<typename Error>
+static void reject_job_promise(JS::NonnullGCPtr<Job> job, FlyString message)
+{
+    // 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.
+    if (job->client) {
+        auto& realm = job->client->realm();
+        HTML::queue_a_task(HTML::Task::Source::DOMManipulation, job->client->responsible_event_loop(), nullptr, JS::create_heap_function(realm.heap(), [&realm, job, message] {
+            HTML::TemporaryExecutionContext const context(*job->client, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
+            WebIDL::reject_promise(realm, *job->job_promise, Error::create(realm, message));
+        }));
+    }
+
+    // 2. For each equivalentJob in job’s list of equivalent jobs:
+    for (auto& equivalent_job : job->list_of_equivalent_jobs) {
+        // 1. If equivalentJob’s client is null, continue.
+        if (!equivalent_job->client)
+            continue;
+
+        // 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.
+        auto& realm = equivalent_job->client->realm();
+        HTML::queue_a_task(HTML::Task::Source::DOMManipulation, equivalent_job->client->responsible_event_loop(), nullptr, JS::create_heap_function(realm.heap(), [&realm, equivalent_job, message] {
+            HTML::TemporaryExecutionContext const context(*equivalent_job->client, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
+            WebIDL::reject_promise(realm, *equivalent_job->job_promise, Error::create(realm, message));
+        }));
+    }
+}
+
+// https://w3c.github.io/ServiceWorker/#schedule-job-algorithm
 void schedule_job(JS::VM& vm, JS::NonnullGCPtr<Job> job)
 {
     // 1. Let jobQueue be null.
     // Note: See below for how we ensure job queue
 
     // 2. Let jobScope be job’s scope url, serialized.
+    // FIXME: Suspect that spec should specify to not use fragment here
     auto job_scope = job->scope_url.serialize();
 
     // 3. If scope to job queue map[jobScope] does not exist, set scope to job queue map[jobScope] to a new job queue.

+ 2 - 2
Userland/Libraries/LibWeb/ServiceWorker/Job.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Andrew Kaster <akaster@ladybird.org>
+ * Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -73,7 +73,7 @@ private:
     Job(Type, StorageAPI::StorageKey, URL::URL scope_url, URL::URL script_url, JS::GCPtr<WebIDL::Promise>, JS::GCPtr<HTML::EnvironmentSettingsObject> client);
 };
 
-// https://w3c.github.io/ServiceWorker/#schedule-job
+// https://w3c.github.io/ServiceWorker/#schedule-job-algorithm
 void schedule_job(JS::VM&, JS::NonnullGCPtr<Job>);
 
 }

+ 98 - 0
Userland/Libraries/LibWeb/ServiceWorker/Registration.cpp

@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/HashMap.h>
+#include <LibWeb/ServiceWorker/Registration.h>
+
+namespace Web::ServiceWorker {
+
+struct RegistrationKey {
+    StorageAPI::StorageKey key;
+    ByteString serialized_scope_url;
+
+    constexpr bool operator==(RegistrationKey const&) const = default;
+};
+
+// FIXME: Surely this needs hooks to be cleared and manipulated at the UA level
+//        Does this need to be serialized to disk as well?
+static HashMap<RegistrationKey, Registration> s_registrations;
+
+Registration::Registration(StorageAPI::StorageKey storage_key, URL::URL scope, Bindings::ServiceWorkerUpdateViaCache update_via_cache)
+    : m_storage_key(move(storage_key))
+    , m_scope_url(move(scope))
+    , m_update_via_cache_mode(update_via_cache)
+{
+}
+
+// https://w3c.github.io/ServiceWorker/#dfn-service-worker-registration-unregistered
+bool Registration::is_unregistered()
+{
+    // A service worker registration is said to be unregistered if registration map[this service worker registration's (storage key, serialized scope url)] is not this service worker registration.
+    // FIXME: Suspect that spec should say to serialize without fragment
+    auto const key = RegistrationKey { m_storage_key, m_scope_url.serialize(URL::ExcludeFragment::Yes) };
+    return s_registrations.get(key).map([](auto& registration) { return &registration; }).value_or(nullptr) != this;
+}
+
+// https://w3c.github.io/ServiceWorker/#get-registration-algorithm
+Optional<Registration&> Registration::get(StorageAPI::StorageKey const& key, Optional<URL::URL> scope)
+{
+    // 1. Run the following steps atomically.
+    // FIXME: What does this mean? Do we need a mutex? does it need to be 'locked' at the UA level?
+
+    // 2. Let scopeString be the empty string.
+    ByteString scope_string;
+
+    // 3. If scope is not null, set scopeString to serialized scope with the exclude fragment flag set.
+    if (scope.has_value())
+        scope_string = scope.value().serialize(URL::ExcludeFragment::Yes);
+
+    // 4. For each (entry storage key, entry scope) → registration of registration map:
+    //   1. If storage key equals entry storage key and scopeString matches entry scope, then return registration.
+    // 5. Return null.
+    return s_registrations.get({ key, scope_string });
+}
+
+// https://w3c.github.io/ServiceWorker/#set-registration-algorithm
+Registration& Registration::set(StorageAPI::StorageKey const& storage_key, URL::URL const& scope, Bindings::ServiceWorkerUpdateViaCache update_via_cache)
+{
+    // FIXME: 1. Run the following steps atomically.
+
+    // 2. Let scopeString be serialized scope with the exclude fragment flag set.
+    // 3. Let registration be a new service worker registration whose storage key is set to storage key, scope url is set to scope, and update via cache mode is set to updateViaCache.
+    // 4. Set registration map[(storage key, scopeString)] to registration.
+    // 5. Return registration.
+
+    // FIXME: Is there a way to "ensure but always replace?"
+    auto key = RegistrationKey { storage_key, scope.serialize(URL::ExcludeFragment::Yes) };
+    (void)s_registrations.set(key, Registration(storage_key, scope, update_via_cache));
+    return s_registrations.get(key).value();
+}
+
+// https://w3c.github.io/ServiceWorker/#get-newest-worker
+ServiceWorker* Registration::newest_worker() const
+{
+    //  FIXME: 1. Run the following steps atomically.
+
+    // 2. Let newestWorker be null.
+    // 3. If registration’s installing worker is not null, set newestWorker to registration’s installing worker.
+    // 4. If registration’s waiting worker is not null, set newestWorker to registration’s waiting worker.
+    // 5. If registration’s active worker is not null, set newestWorker to registration’s active worker.
+    // 6. Return newestWorker.
+    return m_installing_worker ? m_installing_worker : m_waiting_worker ? m_waiting_worker
+                                                                        : m_active_worker;
+}
+
+}
+
+namespace AK {
+template<>
+struct Traits<Web::ServiceWorker::RegistrationKey> : public DefaultTraits<Web::ServiceWorker::RegistrationKey> {
+    static unsigned hash(Web::ServiceWorker::RegistrationKey const& key)
+    {
+        return pair_int_hash(Traits<Web::StorageAPI::StorageKey>::hash(key.key), Traits<ByteString>::hash(key.serialized_scope_url));
+    }
+};
+}

+ 63 - 0
Userland/Libraries/LibWeb/ServiceWorker/Registration.h

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Optional.h>
+#include <AK/Time.h>
+#include <AK/Traits.h>
+#include <LibURL/URL.h>
+#include <LibWeb/Bindings/ServiceWorkerRegistrationPrototype.h>
+#include <LibWeb/ServiceWorker/ServiceWorker.h>
+#include <LibWeb/StorageAPI/StorageKey.h>
+
+namespace Web::ServiceWorker {
+
+// https://w3c.github.io/ServiceWorker/#dfn-service-worker-registration
+// This class corresponds to "service worker registration", not "ServiceWorkerRegistration"
+// FIXME: This object needs to live at the user-agent level, in LibWebView, not in LibWeb
+//        .. And it will need some way to synchronize updates to each 'client' (aka process aka ESO)
+class Registration {
+    AK_MAKE_NONCOPYABLE(Registration);
+    AK_MAKE_DEFAULT_MOVABLE(Registration);
+
+public:
+    // https://w3c.github.io/ServiceWorker/#get-registration-algorithm
+    static Optional<Registration&> get(StorageAPI::StorageKey const&, Optional<URL::URL> scope);
+
+    // https://w3c.github.io/ServiceWorker/#set-registration-algorithm
+    static Registration& set(StorageAPI::StorageKey const&, URL::URL const&, Bindings::ServiceWorkerUpdateViaCache);
+
+    bool is_unregistered();
+
+    StorageAPI::StorageKey const& storage_key() const { return m_storage_key; }
+    URL::URL const& scope_url() const { return m_scope_url; }
+    Bindings::ServiceWorkerUpdateViaCache update_via_cache() const { return m_update_via_cache_mode; }
+
+    ServiceWorker* newest_worker() const;
+
+private:
+    Registration(StorageAPI::StorageKey, URL::URL, Bindings::ServiceWorkerUpdateViaCache);
+
+    StorageAPI::StorageKey m_storage_key; // https://w3c.github.io/ServiceWorker/#service-worker-registration-storage-key
+    URL::URL m_scope_url;                 // https://w3c.github.io/ServiceWorker/#dfn-scope-url
+
+    // NOTE: These are "service workers", not "HTML::ServiceWorker"s
+    ServiceWorker* m_installing_worker { nullptr }; // https://w3c.github.io/ServiceWorker/#dfn-installing-worker
+    ServiceWorker* m_waiting_worker { nullptr };    // https://w3c.github.io/ServiceWorker/#dfn-waiting-worker
+    ServiceWorker* m_active_worker { nullptr };     // https://w3c.github.io/ServiceWorker/#dfn-active-worker
+
+    Optional<MonotonicTime> m_last_update_check_time;                                                               // https://w3c.github.io/ServiceWorker/#dfn-last-update-check-time
+    Bindings::ServiceWorkerUpdateViaCache m_update_via_cache_mode = Bindings::ServiceWorkerUpdateViaCache::Imports; // https://w3c.github.io/ServiceWorker/#dfn-update-via-cache
+    // FIXME: A service worker registration has one or more task queues... https://w3c.github.io/ServiceWorker/#dfn-service-worker-registration-task-queue
+    // FIXME: Spec bug: A service worker registration has an associated NavigationPreloadManager object.
+    //        This can't possibly be true. The association is the other way around.
+
+    bool m_navigation_preload_enabled = { false }; // https://w3c.github.io/ServiceWorker/#service-worker-registration-navigation-preload-enabled-flag
+    ByteString m_navigation_preload_header_value;  // https://w3c.github.io/ServiceWorker/#service-worker-registration-navigation-preload-header-value
+};
+
+}

+ 10 - 0
Userland/Libraries/LibWeb/ServiceWorker/ServiceWorker.cpp

@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/ServiceWorker/ServiceWorker.h>
+
+namespace Web::ServiceWorker {
+}

+ 27 - 0
Userland/Libraries/LibWeb/ServiceWorker/ServiceWorker.h

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2024, Andrew Kaster <andrew@ladybird.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibURL/URL.h>
+#include <LibWeb/Bindings/ServiceWorkerPrototype.h>
+#include <LibWeb/Bindings/WorkerPrototype.h>
+
+namespace Web::ServiceWorker {
+
+// https://w3c.github.io/ServiceWorker/#dfn-service-worker
+// This class corresponds to "service worker", not "ServiceWorker"
+// FIXME: This should be owned and managed at the user agent level
+// FIXME: A lot of the fields for this struct actually need to live in the Agent for the service worker in the WebWorker process
+struct ServiceWorker {
+    Bindings::ServiceWorkerState state = Bindings::ServiceWorkerState::Parsed; // https://w3c.github.io/ServiceWorker/#dfn-state
+    URL::URL script_url;                                                       // https://w3c.github.io/ServiceWorker/#dfn-script-url
+    Bindings::WorkerType worker_type = Bindings::WorkerType::Classic;          // https://w3c.github.io/ServiceWorker/#dfn-type
+
+    // FIXME: A lot more fields after this...
+};
+
+}