/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 | | } |