Coverage Report

Created: 2025-09-05 06:52

/src/serenity/Userland/Libraries/LibWeb/HTML/ServiceWorkerContainer.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
3
 * Copyright (c) 2024, Jamie Mansfield <jmansfield@cadixdev.org>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibJS/Runtime/Realm.h>
9
#include <LibWeb/Bindings/Intrinsics.h>
10
#include <LibWeb/Bindings/ServiceWorkerContainerPrototype.h>
11
#include <LibWeb/DOMURL/DOMURL.h>
12
#include <LibWeb/HTML/EventNames.h>
13
#include <LibWeb/HTML/ServiceWorkerContainer.h>
14
#include <LibWeb/ServiceWorker/Job.h>
15
#include <LibWeb/StorageAPI/StorageKey.h>
16
17
namespace Web::HTML {
18
19
JS_DEFINE_ALLOCATOR(ServiceWorkerContainer);
20
21
ServiceWorkerContainer::ServiceWorkerContainer(JS::Realm& realm)
22
0
    : DOM::EventTarget(realm)
23
0
    , m_service_worker_client(relevant_settings_object(*this))
24
0
{
25
0
}
26
27
0
ServiceWorkerContainer::~ServiceWorkerContainer() = default;
28
29
void ServiceWorkerContainer::initialize(JS::Realm& realm)
30
0
{
31
0
    Base::initialize(realm);
32
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(ServiceWorkerContainer);
33
0
}
34
35
void ServiceWorkerContainer::visit_edges(Cell::Visitor& visitor)
36
0
{
37
0
    Base::visit_edges(visitor);
38
0
    visitor.visit(m_service_worker_client);
39
0
}
40
41
JS::NonnullGCPtr<ServiceWorkerContainer> ServiceWorkerContainer::create(JS::Realm& realm)
42
0
{
43
0
    return realm.heap().allocate<ServiceWorkerContainer>(realm, realm);
44
0
}
45
46
// https://w3c.github.io/ServiceWorker/#navigator-service-worker-register
47
JS::NonnullGCPtr<JS::Promise> ServiceWorkerContainer::register_(String script_url, RegistrationOptions const& options)
48
0
{
49
0
    auto& realm = this->realm();
50
    // Note: The register(scriptURL, options) method creates or updates a service worker registration for the given scope url.
51
    // If successful, a service worker registration ties the provided scriptURL to a scope url,
52
    // which is subsequently used for navigation matching.
53
54
    // 1. Let p be a promise.
55
0
    auto p = WebIDL::create_promise(realm);
56
57
    // FIXME: 2. Set scriptURL to the result of invoking Get Trusted Type compliant string with TrustedScriptURL,
58
    //    this's relevant global object, scriptURL, "ServiceWorkerContainer register", and "script".
59
60
    // 3 Let client be this's service worker client.
61
0
    auto client = m_service_worker_client;
62
63
    // 4. Let scriptURL be the result of parsing scriptURL with this's relevant settings object’s API base URL.
64
0
    auto base_url = relevant_settings_object(*this).api_base_url();
65
0
    auto parsed_script_url = DOMURL::parse(script_url, base_url);
66
67
    // 5. Let scopeURL be null.
68
0
    Optional<URL::URL> scope_url;
69
70
    // 6. If options["scope"] exists, set scopeURL to the result of parsing options["scope"] with this's relevant settings object’s API base URL.
71
0
    if (options.scope.has_value()) {
72
0
        scope_url = DOMURL::parse(options.scope.value(), base_url);
73
0
    }
74
75
    // 7. Invoke Start Register with scopeURL, scriptURL, p, client, client’s creation URL, options["type"], and options["updateViaCache"].
76
0
    start_register(scope_url, parsed_script_url, p, client, client->creation_url, options.type, options.update_via_cache);
77
78
    // 8. Return p.
79
0
    return verify_cast<JS::Promise>(*p->promise());
80
0
}
81
82
// https://w3c.github.io/ServiceWorker/#start-register-algorithm
83
void ServiceWorkerContainer::start_register(Optional<URL::URL> scope_url, URL::URL script_url, JS::NonnullGCPtr<WebIDL::Promise> promise, EnvironmentSettingsObject& client, URL::URL referrer, Bindings::WorkerType worker_type, Bindings::ServiceWorkerUpdateViaCache update_via_cache)
84
0
{
85
0
    auto& realm = this->realm();
86
0
    auto& vm = realm.vm();
87
88
    // 1. If scriptURL is failure, reject promise with a TypeError and abort these steps.
89
0
    if (!script_url.is_valid()) {
90
0
        WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL is not a valid URL"sv));
91
0
        return;
92
0
    }
93
94
    // 2. Set scriptURL’s fragment to null.
95
    // Note:  The user agent does not store the fragment of the script’s url.
96
    //        This means that the fragment does not have an effect on identifying service workers.
97
0
    script_url.set_fragment({});
98
99
    // 3. If scriptURL’s scheme is not one of "http" and "https", reject promise with a TypeError and abort these steps.
100
0
    if (!script_url.scheme().is_one_of("http"sv, "https"sv)) {
101
0
        WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL must have a scheme of 'http' or 'https'"sv));
102
0
        return;
103
0
    }
104
105
    // 4. If any of the strings in scriptURL’s path contains either ASCII case-insensitive "%2f" or ASCII case-insensitive "%5c",
106
    //    reject promise with a TypeError and abort these steps.
107
0
    auto invalid_path = script_url.paths().first_matching([&](auto& path) {
108
0
        return path.contains("%2f"sv, CaseSensitivity::CaseInsensitive) || path.contains("%5c"sv, CaseSensitivity::CaseInsensitive);
109
0
    });
110
0
    if (invalid_path.has_value()) {
111
0
        WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scriptURL path must not contain '%2f' or '%5c'"sv));
112
0
        return;
113
0
    }
114
115
    // 5. If scopeURL is null, set scopeURL to the result of parsing the string "./" with scriptURL.
116
    // Note: The scope url for the registration is set to the location of the service worker script by default.
117
0
    if (!scope_url.has_value()) {
118
0
        scope_url = DOMURL::parse("./"sv, script_url);
119
0
    }
120
121
    // 6. If scopeURL is failure, reject promise with a TypeError and abort these steps.
122
0
    if (!scope_url->is_valid()) {
123
0
        WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL is not a valid URL"sv));
124
0
        return;
125
0
    }
126
127
    // 7. Set scopeURL’s fragment to null.
128
    // Note: The user agent does not store the fragment of the scope url.
129
    //       This means that the fragment does not have an effect on identifying service worker registrations.
130
0
    scope_url->set_fragment({});
131
132
    // 8. If scopeURL’s scheme is not one of "http" and "https", reject promise with a TypeError and abort these steps.
133
0
    if (!scope_url->scheme().is_one_of("http"sv, "https"sv)) {
134
0
        WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL must have a scheme of 'http' or 'https'"sv));
135
0
        return;
136
0
    }
137
138
    // 9. If any of the strings in scopeURL’s path contains either ASCII case-insensitive "%2f" or ASCII case-insensitive "%5c",
139
    //    reject promise with a TypeError and abort these steps.
140
0
    invalid_path = scope_url->paths().first_matching([&](auto& path) {
141
0
        return path.contains("%2f"sv, CaseSensitivity::CaseInsensitive) || path.contains("%5c"sv, CaseSensitivity::CaseInsensitive);
142
0
    });
143
0
    if (invalid_path.has_value()) {
144
0
        WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "scopeURL path must not contain '%2f' or '%5c'"sv));
145
0
        return;
146
0
    }
147
148
    // 10. Let storage key be the result of running obtain a storage key given client.
149
0
    auto storage_key = StorageAPI::obtain_a_storage_key(client);
150
151
    // FIXME: Ad-Hoc. Spec should handle this failure here, or earlier.
152
0
    if (!storage_key.has_value()) {
153
0
        WebIDL::reject_promise(realm, promise, JS::TypeError::create(realm, "Failed to obtain a storage key"sv));
154
0
        return;
155
0
    }
156
157
    // 11. Let job be the result of running Create Job with register, storage key, scopeURL, scriptURL, promise, and client.
158
0
    auto job = ServiceWorker::Job::create(vm, ServiceWorker::Job::Type::Register, storage_key.value(), scope_url.value(), script_url, promise, client);
159
160
    // 12. Set job’s worker type to workerType.
161
0
    job->worker_type = worker_type;
162
163
    // 13. Set job’s update via cache to updateViaCache.
164
0
    job->update_via_cache = update_via_cache;
165
166
    // 14. Set job’s referrer to referrer.
167
0
    job->referrer = move(referrer);
168
169
    // 15. Invoke Schedule Job with job.
170
0
    ServiceWorker::schedule_job(vm, job);
171
0
}
172
173
#undef __ENUMERATE
174
#define __ENUMERATE(attribute_name, event_name)                                    \
175
    void ServiceWorkerContainer::set_##attribute_name(WebIDL::CallbackType* value) \
176
0
    {                                                                              \
177
0
        set_event_handler_attribute(event_name, move(value));                      \
178
0
    }                                                                              \
Unexecuted instantiation: Web::HTML::ServiceWorkerContainer::set_oncontrollerchange(Web::WebIDL::CallbackType*)
Unexecuted instantiation: Web::HTML::ServiceWorkerContainer::set_onmessage(Web::WebIDL::CallbackType*)
Unexecuted instantiation: Web::HTML::ServiceWorkerContainer::set_onmessageerror(Web::WebIDL::CallbackType*)
179
    WebIDL::CallbackType* ServiceWorkerContainer::attribute_name()                 \
180
0
    {                                                                              \
181
0
        return event_handler_attribute(event_name);                                \
182
0
    }
Unexecuted instantiation: Web::HTML::ServiceWorkerContainer::oncontrollerchange()
Unexecuted instantiation: Web::HTML::ServiceWorkerContainer::onmessage()
Unexecuted instantiation: Web::HTML::ServiceWorkerContainer::onmessageerror()
183
ENUMERATE_SERVICE_WORKER_CONTAINER_EVENT_HANDLERS(__ENUMERATE)
184
#undef __ENUMERATE
185
186
}