/src/serenity/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2022-2023, networkException <networkexception@serenityos.org> |
3 | | * Copyright (c) 2024, Tim Ledbetter <timledbetter@gmail.com> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <LibCore/EventLoop.h> |
9 | | #include <LibJS/Heap/HeapFunction.h> |
10 | | #include <LibJS/Runtime/ModuleRequest.h> |
11 | | #include <LibTextCodec/Decoder.h> |
12 | | #include <LibWeb/DOM/Document.h> |
13 | | #include <LibWeb/DOMURL/DOMURL.h> |
14 | | #include <LibWeb/Fetch/Fetching/Fetching.h> |
15 | | #include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h> |
16 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h> |
17 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h> |
18 | | #include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h> |
19 | | #include <LibWeb/Fetch/Infrastructure/URL.h> |
20 | | #include <LibWeb/HTML/HTMLScriptElement.h> |
21 | | #include <LibWeb/HTML/PotentialCORSRequest.h> |
22 | | #include <LibWeb/HTML/Scripting/ClassicScript.h> |
23 | | #include <LibWeb/HTML/Scripting/Environments.h> |
24 | | #include <LibWeb/HTML/Scripting/Fetching.h> |
25 | | #include <LibWeb/HTML/Scripting/ModuleScript.h> |
26 | | #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h> |
27 | | #include <LibWeb/HTML/Window.h> |
28 | | #include <LibWeb/Infra/Strings.h> |
29 | | #include <LibWeb/MimeSniff/MimeType.h> |
30 | | |
31 | | namespace Web::HTML { |
32 | | |
33 | | JS_DEFINE_ALLOCATOR(FetchContext); |
34 | | |
35 | | OnFetchScriptComplete create_on_fetch_script_complete(JS::Heap& heap, Function<void(JS::GCPtr<Script>)> function) |
36 | 0 | { |
37 | 0 | return JS::create_heap_function(heap, move(function)); |
38 | 0 | } |
39 | | |
40 | | PerformTheFetchHook create_perform_the_fetch_hook(JS::Heap& heap, Function<WebIDL::ExceptionOr<void>(JS::NonnullGCPtr<Fetch::Infrastructure::Request>, TopLevelModule, Fetch::Infrastructure::FetchAlgorithms::ProcessResponseConsumeBodyFunction)> function) |
41 | 0 | { |
42 | 0 | return JS::create_heap_function(heap, move(function)); |
43 | 0 | } |
44 | | |
45 | | ScriptFetchOptions default_classic_script_fetch_options() |
46 | 0 | { |
47 | | // The default classic script fetch options are a script fetch options whose cryptographic nonce is the empty string, |
48 | | // integrity metadata is the empty string, parser metadata is "not-parser-inserted", credentials mode is "same-origin", |
49 | | // referrer policy is the empty string, and fetch priority is "auto". |
50 | 0 | return ScriptFetchOptions { |
51 | 0 | .cryptographic_nonce = {}, |
52 | 0 | .integrity_metadata = {}, |
53 | 0 | .parser_metadata = Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted, |
54 | 0 | .credentials_mode = Fetch::Infrastructure::Request::CredentialsMode::SameOrigin, |
55 | 0 | .referrer_policy = {}, |
56 | 0 | .fetch_priority = Fetch::Infrastructure::Request::Priority::Auto |
57 | 0 | }; |
58 | 0 | } |
59 | | |
60 | | // https://html.spec.whatwg.org/multipage/webappapis.html#module-type-from-module-request |
61 | | ByteString module_type_from_module_request(JS::ModuleRequest const& module_request) |
62 | 0 | { |
63 | | // 1. Let moduleType be "javascript". |
64 | 0 | ByteString module_type = "javascript"sv; |
65 | | |
66 | | // 2. If moduleRequest.[[Attributes]] has a Record entry such that entry.[[Key]] is "type", then: |
67 | 0 | for (auto const& entry : module_request.attributes) { |
68 | 0 | if (entry.key != "type"sv) |
69 | 0 | continue; |
70 | | |
71 | | // 1. If entry.[[Value]] is "javascript", then set moduleType to null. |
72 | 0 | if (entry.value == "javascript"sv) |
73 | 0 | module_type = nullptr; |
74 | | // 2. Otherwise, set moduleType to entry.[[Value]]. |
75 | 0 | else |
76 | 0 | module_type = entry.value; |
77 | 0 | } |
78 | | |
79 | | // 3. Return moduleType. |
80 | 0 | return module_type; |
81 | 0 | } |
82 | | |
83 | | // https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier |
84 | | WebIDL::ExceptionOr<URL::URL> resolve_module_specifier(Optional<Script&> referring_script, ByteString const& specifier) |
85 | 0 | { |
86 | | // 1. Let settingsObject and baseURL be null. |
87 | 0 | Optional<EnvironmentSettingsObject&> settings_object; |
88 | 0 | Optional<URL::URL const&> base_url; |
89 | | |
90 | | // 2. If referringScript is not null, then: |
91 | 0 | if (referring_script.has_value()) { |
92 | | // 1. Set settingsObject to referringScript's settings object. |
93 | 0 | settings_object = referring_script->settings_object(); |
94 | | |
95 | | // 2. Set baseURL to referringScript's base URL. |
96 | 0 | base_url = referring_script->base_url(); |
97 | 0 | } |
98 | | // 3. Otherwise: |
99 | 0 | else { |
100 | | // 1. Assert: there is a current settings object. |
101 | | // NOTE: This is handled by the current_settings_object() accessor. |
102 | | |
103 | | // 2. Set settingsObject to the current settings object. |
104 | 0 | settings_object = current_settings_object(); |
105 | | |
106 | | // 3. Set baseURL to settingsObject's API base URL. |
107 | 0 | base_url = settings_object->api_base_url(); |
108 | 0 | } |
109 | | |
110 | | // 4. Let importMap be an empty import map. |
111 | 0 | ImportMap import_map; |
112 | | |
113 | | // 5. If settingsObject's global object implements Window, then set importMap to settingsObject's global object's import map. |
114 | 0 | if (is<Window>(settings_object->global_object())) |
115 | 0 | import_map = verify_cast<Window>(settings_object->global_object()).import_map(); |
116 | | |
117 | | // 6. Let baseURLString be baseURL, serialized. |
118 | 0 | auto base_url_string = base_url->serialize(); |
119 | | |
120 | | // 7. Let asURL be the result of resolving a URL-like module specifier given specifier and baseURL. |
121 | 0 | auto as_url = resolve_url_like_module_specifier(specifier, *base_url); |
122 | | |
123 | | // 8. Let normalizedSpecifier be the serialization of asURL, if asURL is non-null; otherwise, specifier. |
124 | 0 | auto normalized_specifier = as_url.has_value() ? as_url->serialize() : specifier; |
125 | | |
126 | | // 9. For each scopePrefix → scopeImports of importMap's scopes: |
127 | 0 | for (auto const& entry : import_map.scopes()) { |
128 | | // FIXME: Clarify if the serialization steps need to be run here. The steps below assume |
129 | | // scopePrefix to be a string. |
130 | 0 | auto const& scope_prefix = entry.key.serialize(); |
131 | 0 | auto const& scope_imports = entry.value; |
132 | | |
133 | | // 1. If scopePrefix is baseURLString, or if scopePrefix ends with U+002F (/) and scopePrefix is a code unit prefix of baseURLString, then: |
134 | 0 | if (scope_prefix == base_url_string || (scope_prefix.ends_with("/"sv) && Infra::is_code_unit_prefix(scope_prefix, base_url_string))) { |
135 | | // 1. Let scopeImportsMatch be the result of resolving an imports match given normalizedSpecifier, asURL, and scopeImports. |
136 | 0 | auto scope_imports_match = TRY(resolve_imports_match(normalized_specifier, as_url, scope_imports)); |
137 | | |
138 | | // 2. If scopeImportsMatch is not null, then return scopeImportsMatch. |
139 | 0 | if (scope_imports_match.has_value()) |
140 | 0 | return scope_imports_match.release_value(); |
141 | 0 | } |
142 | 0 | } |
143 | | |
144 | | // 10. Let topLevelImportsMatch be the result of resolving an imports match given normalizedSpecifier, asURL, and importMap's imports. |
145 | 0 | auto top_level_imports_match = TRY(resolve_imports_match(normalized_specifier, as_url, import_map.imports())); |
146 | | |
147 | | // 11. If topLevelImportsMatch is not null, then return topLevelImportsMatch. |
148 | 0 | if (top_level_imports_match.has_value()) |
149 | 0 | return top_level_imports_match.release_value(); |
150 | | |
151 | | // 12. If asURL is not null, then return asURL. |
152 | 0 | if (as_url.has_value()) |
153 | 0 | return as_url.release_value(); |
154 | | |
155 | | // 13. Throw a TypeError indicating that specifier was a bare specifier, but was not remapped to anything by importMap. |
156 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Failed to resolve non relative module specifier '{}' from an import map.", specifier).release_value_but_fixme_should_propagate_errors() }; |
157 | 0 | } |
158 | | |
159 | | // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-an-imports-match |
160 | | WebIDL::ExceptionOr<Optional<URL::URL>> resolve_imports_match(ByteString const& normalized_specifier, Optional<URL::URL> as_url, ModuleSpecifierMap const& specifier_map) |
161 | 0 | { |
162 | | // 1. For each specifierKey → resolutionResult of specifierMap: |
163 | 0 | for (auto const& [specifier_key, resolution_result] : specifier_map) { |
164 | | // 1. If specifierKey is normalizedSpecifier, then: |
165 | 0 | if (specifier_key == normalized_specifier) { |
166 | | // 1. If resolutionResult is null, then throw a TypeError indicating that resolution of specifierKey was blocked by a null entry. |
167 | 0 | if (!resolution_result.has_value()) |
168 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Import resolution of '{}' was blocked by a null entry.", specifier_key).release_value_but_fixme_should_propagate_errors() }; |
169 | | |
170 | | // 2. Assert: resolutionResult is a URL. |
171 | 0 | VERIFY(resolution_result->is_valid()); |
172 | | |
173 | | // 3. Return resolutionResult. |
174 | 0 | return resolution_result; |
175 | 0 | } |
176 | | |
177 | | // 2. If all of the following are true: |
178 | 0 | if ( |
179 | | // - specifierKey ends with U+002F (/); |
180 | 0 | specifier_key.ends_with("/"sv) && |
181 | | // - specifierKey is a code unit prefix of normalizedSpecifier; and |
182 | 0 | Infra::is_code_unit_prefix(specifier_key, normalized_specifier) && |
183 | | // - either asURL is null, or asURL is special, |
184 | 0 | (!as_url.has_value() || as_url->is_special()) |
185 | | // then: |
186 | 0 | ) { |
187 | | // 1. If resolutionResult is null, then throw a TypeError indicating that the resolution of specifierKey was blocked by a null entry. |
188 | 0 | if (!resolution_result.has_value()) |
189 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Import resolution of '{}' was blocked by a null entry.", specifier_key).release_value_but_fixme_should_propagate_errors() }; |
190 | | |
191 | | // 2. Assert: resolutionResult is a URL. |
192 | 0 | VERIFY(resolution_result->is_valid()); |
193 | | |
194 | | // 3. Let afterPrefix be the portion of normalizedSpecifier after the initial specifierKey prefix. |
195 | | // FIXME: Clarify if this is meant by the portion after the initial specifierKey prefix. |
196 | 0 | auto after_prefix = normalized_specifier.substring(specifier_key.length()); |
197 | | |
198 | | // 4. Assert: resolutionResult, serialized, ends with U+002F (/), as enforced during parsing. |
199 | 0 | VERIFY(resolution_result->serialize().ends_with("/"sv)); |
200 | | |
201 | | // 5. Let url be the result of URL parsing afterPrefix with resolutionResult. |
202 | 0 | auto url = DOMURL::parse(after_prefix, *resolution_result); |
203 | | |
204 | | // 6. If url is failure, then throw a TypeError indicating that resolution of normalizedSpecifier was blocked since the afterPrefix portion |
205 | | // could not be URL-parsed relative to the resolutionResult mapped to by the specifierKey prefix. |
206 | 0 | if (!url.is_valid()) |
207 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Could not resolve '{}' as the after prefix portion could not be URL-parsed.", normalized_specifier).release_value_but_fixme_should_propagate_errors() }; |
208 | | |
209 | | // 7. Assert: url is a URL. |
210 | 0 | VERIFY(url.is_valid()); |
211 | | |
212 | | // 8. If the serialization of resolutionResult is not a code unit prefix of the serialization of url, then throw a TypeError indicating |
213 | | // that the resolution of normalizedSpecifier was blocked due to it backtracking above its prefix specifierKey. |
214 | 0 | if (!Infra::is_code_unit_prefix(resolution_result->serialize(), url.serialize())) |
215 | 0 | return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, String::formatted("Could not resolve '{}' as it backtracks above its prefix specifierKey.", normalized_specifier).release_value_but_fixme_should_propagate_errors() }; |
216 | | |
217 | | // 9. Return url. |
218 | 0 | return url; |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | | // 2. Return null. |
223 | 0 | return Optional<URL::URL> {}; |
224 | 0 | } |
225 | | |
226 | | // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-url-like-module-specifier |
227 | | Optional<URL::URL> resolve_url_like_module_specifier(ByteString const& specifier, URL::URL const& base_url) |
228 | 0 | { |
229 | | // 1. If specifier starts with "/", "./", or "../", then: |
230 | 0 | if (specifier.starts_with("/"sv) || specifier.starts_with("./"sv) || specifier.starts_with("../"sv)) { |
231 | | // 1. Let url be the result of URL parsing specifier with baseURL. |
232 | 0 | auto url = DOMURL::parse(specifier, base_url); |
233 | | |
234 | | // 2. If url is failure, then return null. |
235 | 0 | if (!url.is_valid()) |
236 | 0 | return {}; |
237 | | |
238 | | // 3. Return url. |
239 | 0 | return url; |
240 | 0 | } |
241 | | |
242 | | // 2. Let url be the result of URL parsing specifier (with no base URL). |
243 | 0 | auto url = DOMURL::parse(specifier); |
244 | | |
245 | | // 3. If url is failure, then return null. |
246 | 0 | if (!url.is_valid()) |
247 | 0 | return {}; |
248 | | |
249 | | // 4. Return url. |
250 | 0 | return url; |
251 | 0 | } |
252 | | |
253 | | // https://html.spec.whatwg.org/multipage/webappapis.html#set-up-the-classic-script-request |
254 | | static void set_up_classic_script_request(Fetch::Infrastructure::Request& request, ScriptFetchOptions const& options) |
255 | 0 | { |
256 | | // Set request's cryptographic nonce metadata to options's cryptographic nonce, its integrity metadata to options's |
257 | | // integrity metadata, its parser metadata to options's parser metadata, its referrer policy to options's referrer |
258 | | // policy, its render-blocking to options's render-blocking, and its priority to options's fetch priority. |
259 | 0 | request.set_cryptographic_nonce_metadata(options.cryptographic_nonce); |
260 | 0 | request.set_integrity_metadata(options.integrity_metadata); |
261 | 0 | request.set_parser_metadata(options.parser_metadata); |
262 | 0 | request.set_referrer_policy(options.referrer_policy); |
263 | 0 | request.set_render_blocking(options.render_blocking); |
264 | 0 | request.set_priority(options.fetch_priority); |
265 | 0 | } |
266 | | |
267 | | // https://html.spec.whatwg.org/multipage/webappapis.html#set-up-the-module-script-request |
268 | | static void set_up_module_script_request(Fetch::Infrastructure::Request& request, ScriptFetchOptions const& options) |
269 | 0 | { |
270 | | // Set request's cryptographic nonce metadata to options's cryptographic nonce, its integrity metadata to options's |
271 | | // integrity metadata, its parser metadata to options's parser metadata, its credentials mode to options's credentials |
272 | | // mode, its referrer policy to options's referrer policy, its render-blocking to options's render-blocking, and its |
273 | | // priority to options's fetch priority. |
274 | 0 | request.set_cryptographic_nonce_metadata(options.cryptographic_nonce); |
275 | 0 | request.set_integrity_metadata(options.integrity_metadata); |
276 | 0 | request.set_parser_metadata(options.parser_metadata); |
277 | 0 | request.set_credentials_mode(options.credentials_mode); |
278 | 0 | request.set_referrer_policy(options.referrer_policy); |
279 | 0 | request.set_render_blocking(options.render_blocking); |
280 | 0 | request.set_priority(options.fetch_priority); |
281 | 0 | } |
282 | | |
283 | | // https://html.spec.whatwg.org/multipage/webappapis.html#get-the-descendant-script-fetch-options |
284 | | WebIDL::ExceptionOr<ScriptFetchOptions> get_descendant_script_fetch_options(ScriptFetchOptions const& original_options, URL::URL const& url, EnvironmentSettingsObject& settings_object) |
285 | 0 | { |
286 | | // 1. Let newOptions be a copy of originalOptions. |
287 | 0 | auto new_options = original_options; |
288 | | |
289 | | // 2. Let integrity be the empty string. |
290 | 0 | String integrity; |
291 | | |
292 | | // 3. If settingsObject's global object is a Window object, then set integrity to the result of resolving a module integrity metadata with url and settingsObject. |
293 | 0 | if (is<Window>(settings_object.global_object())) |
294 | 0 | integrity = TRY(resolve_a_module_integrity_metadata(url, settings_object)); |
295 | | |
296 | | // 4. Set newOptions's integrity metadata to integrity. |
297 | 0 | new_options.integrity_metadata = integrity; |
298 | | |
299 | | // 5. Set newOptions's fetch priority to "auto". |
300 | 0 | new_options.fetch_priority = Fetch::Infrastructure::Request::Priority::Auto; |
301 | | |
302 | | // 6. Return newOptions. |
303 | 0 | return new_options; |
304 | 0 | } |
305 | | |
306 | | // https://html.spec.whatwg.org/multipage/webappapis.html#resolving-a-module-integrity-metadata |
307 | | WebIDL::ExceptionOr<String> resolve_a_module_integrity_metadata(const URL::URL& url, EnvironmentSettingsObject& settings_object) |
308 | 0 | { |
309 | | // 1. Assert: settingsObject's global object is a Window object. |
310 | 0 | VERIFY(is<Window>(settings_object.global_object())); |
311 | | |
312 | | // 2. Let map be settingsObject's global object's import map. |
313 | 0 | auto map = static_cast<Window const&>(settings_object.global_object()).import_map(); |
314 | | |
315 | | // 3. If map's integrity[url] does not exist, then return the empty string. |
316 | | // 4. Return map's integrity[url]. |
317 | 0 | return MUST(String::from_byte_string(map.integrity().get(url).value_or(""))); |
318 | 0 | } |
319 | | |
320 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script |
321 | | WebIDL::ExceptionOr<void> fetch_classic_script(JS::NonnullGCPtr<HTMLScriptElement> element, URL::URL const& url, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete) |
322 | 0 | { |
323 | 0 | auto& realm = element->realm(); |
324 | 0 | auto& vm = realm.vm(); |
325 | | |
326 | | // 1. Let request be the result of creating a potential-CORS request given url, "script", and CORS setting. |
327 | 0 | auto request = create_potential_CORS_request(vm, url, Fetch::Infrastructure::Request::Destination::Script, cors_setting); |
328 | | |
329 | | // 2. Set request's client to settings object. |
330 | 0 | request->set_client(&settings_object); |
331 | | |
332 | | // 3. Set request's initiator type to "script". |
333 | 0 | request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Script); |
334 | | |
335 | | // 4. Set up the classic script request given request and options. |
336 | 0 | set_up_classic_script_request(*request, options); |
337 | | |
338 | | // 5. Fetch request with the following processResponseConsumeBody steps given response response and null, failure, |
339 | | // or a byte sequence bodyBytes: |
340 | 0 | Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; |
341 | 0 | fetch_algorithms_input.process_response_consume_body = [&settings_object, options = move(options), character_encoding = move(character_encoding), on_complete = move(on_complete)](auto response, auto body_bytes) { |
342 | | // 1. Set response to response's unsafe response. |
343 | 0 | response = response->unsafe_response(); |
344 | | |
345 | | // 2. If either of the following conditions are met: |
346 | | // - bodyBytes is null or failure; or |
347 | | // - response's status is not an ok status, |
348 | 0 | if (body_bytes.template has<Empty>() || body_bytes.template has<Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag>() || !Fetch::Infrastructure::is_ok_status(response->status())) { |
349 | | // then run onComplete given null, and abort these steps. |
350 | 0 | on_complete->function()(nullptr); |
351 | 0 | return; |
352 | 0 | } |
353 | | |
354 | | // 3. Let potentialMIMETypeForEncoding be the result of extracting a MIME type given response's header list. |
355 | 0 | auto potential_mime_type_for_encoding = response->header_list()->extract_mime_type(); |
356 | | |
357 | | // 4. Set character encoding to the result of legacy extracting an encoding given potentialMIMETypeForEncoding |
358 | | // and character encoding. |
359 | 0 | auto extracted_character_encoding = Fetch::Infrastructure::legacy_extract_an_encoding(potential_mime_type_for_encoding, character_encoding); |
360 | | |
361 | | // 5. Let source text be the result of decoding bodyBytes to Unicode, using character encoding as the fallback |
362 | | // encoding. |
363 | 0 | auto fallback_decoder = TextCodec::decoder_for(extracted_character_encoding); |
364 | 0 | VERIFY(fallback_decoder.has_value()); |
365 | | |
366 | 0 | auto source_text = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*fallback_decoder, body_bytes.template get<ByteBuffer>()).release_value_but_fixme_should_propagate_errors(); |
367 | | |
368 | | // 6. Let muted errors be true if response was CORS-cross-origin, and false otherwise. |
369 | 0 | auto muted_errors = response->is_cors_cross_origin() ? ClassicScript::MutedErrors::Yes : ClassicScript::MutedErrors::No; |
370 | | |
371 | | // 7. Let script be the result of creating a classic script given source text, settings object, response's URL, |
372 | | // options, and muted errors. |
373 | | // FIXME: Pass options. |
374 | 0 | auto response_url = response->url().value_or({}); |
375 | 0 | auto script = ClassicScript::create(response_url.to_byte_string(), source_text, settings_object, response_url, 1, muted_errors); |
376 | | |
377 | | // 8. Run onComplete given script. |
378 | 0 | on_complete->function()(script); |
379 | 0 | }; |
380 | |
|
381 | 0 | TRY(Fetch::Fetching::fetch(element->realm(), request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); |
382 | 0 | return {}; |
383 | 0 | } |
384 | | |
385 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-worker-script |
386 | | WebIDL::ExceptionOr<void> fetch_classic_worker_script(URL::URL const& url, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook perform_fetch, OnFetchScriptComplete on_complete) |
387 | 0 | { |
388 | 0 | auto& realm = settings_object.realm(); |
389 | 0 | auto& vm = realm.vm(); |
390 | | |
391 | | // 1. Let request be a new request whose URL is url, client is fetchClient, destination is destination, initiator type is "other", |
392 | | // mode is "same-origin", credentials mode is "same-origin", parser metadata is "not parser-inserted", |
393 | | // and whose use-URL-credentials flag is set. |
394 | 0 | auto request = Fetch::Infrastructure::Request::create(vm); |
395 | 0 | request->set_url(url); |
396 | 0 | request->set_client(&fetch_client); |
397 | 0 | request->set_destination(destination); |
398 | 0 | request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Other); |
399 | | |
400 | | // FIXME: Use proper SameOrigin CORS mode once Origins are set properly in WorkerHost processes |
401 | 0 | request->set_mode(Fetch::Infrastructure::Request::Mode::NoCORS); |
402 | |
|
403 | 0 | request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::SameOrigin); |
404 | 0 | request->set_parser_metadata(Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted); |
405 | 0 | request->set_use_url_credentials(true); |
406 | |
|
407 | 0 | auto process_response_consume_body = [&settings_object, on_complete = move(on_complete)](auto response, auto body_bytes) { |
408 | | // 1. Set response to response's unsafe response. |
409 | 0 | response = response->unsafe_response(); |
410 | | |
411 | | // 2. If either of the following conditions are met: |
412 | | // - bodyBytes is null or failure; or |
413 | | // - response's status is not an ok status, |
414 | 0 | if (body_bytes.template has<Empty>() || body_bytes.template has<Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag>() || !Fetch::Infrastructure::is_ok_status(response->status())) { |
415 | | // then run onComplete given null, and abort these steps. |
416 | 0 | on_complete->function()(nullptr); |
417 | 0 | return; |
418 | 0 | } |
419 | | |
420 | | // 3. If all of the following are true: |
421 | | // - response's URL's scheme is an HTTP(S) scheme; and |
422 | | // - the result of extracting a MIME type from response's header list is not a JavaScript MIME type, |
423 | 0 | auto maybe_mime_type = response->header_list()->extract_mime_type(); |
424 | 0 | auto mime_type_is_javascript = maybe_mime_type.has_value() && maybe_mime_type->is_javascript(); |
425 | |
|
426 | 0 | if (response->url().has_value() && Fetch::Infrastructure::is_http_or_https_scheme(response->url()->scheme()) && !mime_type_is_javascript) { |
427 | 0 | auto mime_type_serialized = maybe_mime_type.has_value() ? maybe_mime_type->serialized() : "unknown"_string; |
428 | 0 | dbgln("Invalid non-javascript mime type \"{}\" for worker script at {}", mime_type_serialized, response->url().value()); |
429 | | |
430 | | // then run onComplete given null, and abort these steps. |
431 | 0 | on_complete->function()(nullptr); |
432 | 0 | return; |
433 | 0 | } |
434 | | // NOTE: Other fetch schemes are exempted from MIME type checking for historical web-compatibility reasons. |
435 | | // We might be able to tighten this in the future; see https://github.com/whatwg/html/issues/3255. |
436 | | |
437 | | // 4. Let sourceText be the result of UTF-8 decoding bodyBytes. |
438 | 0 | auto decoder = TextCodec::decoder_for("UTF-8"sv); |
439 | 0 | VERIFY(decoder.has_value()); |
440 | 0 | auto source_text = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, body_bytes.template get<ByteBuffer>()).release_value_but_fixme_should_propagate_errors(); |
441 | | |
442 | | // 5. Let script be the result of creating a classic script using sourceText, settingsObject, |
443 | | // response's URL, and the default classic script fetch options. |
444 | 0 | auto response_url = response->url().value_or({}); |
445 | 0 | auto script = ClassicScript::create(response_url.to_byte_string(), source_text, settings_object, response_url); |
446 | | |
447 | | // 6. Run onComplete given script. |
448 | 0 | on_complete->function()(script); |
449 | 0 | }; |
450 | | |
451 | | // 2. If performFetch was given, run performFetch with request, true, and with processResponseConsumeBody as defined below. |
452 | 0 | if (perform_fetch != nullptr) { |
453 | 0 | TRY(perform_fetch->function()(request, TopLevelModule::Yes, move(process_response_consume_body))); |
454 | 0 | } |
455 | | |
456 | | // Otherwise, fetch request with processResponseConsumeBody set to processResponseConsumeBody as defined below. |
457 | 0 | else { |
458 | 0 | Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; |
459 | 0 | fetch_algorithms_input.process_response_consume_body = move(process_response_consume_body); |
460 | 0 | TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); |
461 | 0 | } |
462 | 0 | return {}; |
463 | 0 | } |
464 | | |
465 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-worker-imported-script |
466 | | WebIDL::ExceptionOr<JS::NonnullGCPtr<ClassicScript>> fetch_a_classic_worker_imported_script(URL::URL const& url, HTML::EnvironmentSettingsObject& settings_object, PerformTheFetchHook perform_fetch) |
467 | 0 | { |
468 | 0 | auto& realm = settings_object.realm(); |
469 | 0 | auto& vm = realm.vm(); |
470 | | |
471 | | // 1. Let response be null. |
472 | 0 | JS::GCPtr<Fetch::Infrastructure::Response> response = nullptr; |
473 | | |
474 | | // 2. Let bodyBytes be null. |
475 | 0 | Fetch::Infrastructure::FetchAlgorithms::BodyBytes body_bytes; |
476 | | |
477 | | // 3. Let request be a new request whose URL is url, client is settingsObject, destination is "script", initiator type is "other", |
478 | | // parser metadata is "not parser-inserted", and whose use-URL-credentials flag is set. |
479 | 0 | auto request = Fetch::Infrastructure::Request::create(vm); |
480 | 0 | request->set_url(url); |
481 | 0 | request->set_client(&settings_object); |
482 | 0 | request->set_destination(Fetch::Infrastructure::Request::Destination::Script); |
483 | 0 | request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Other); |
484 | 0 | request->set_parser_metadata(Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted); |
485 | 0 | request->set_use_url_credentials(true); |
486 | |
|
487 | 0 | auto process_response_consume_body = [&response, &body_bytes](JS::NonnullGCPtr<Fetch::Infrastructure::Response> res, Fetch::Infrastructure::FetchAlgorithms::BodyBytes bb) { |
488 | | // 1. Set bodyBytes to bb. |
489 | 0 | body_bytes = move(bb); |
490 | | |
491 | | // 2. Set response to res. |
492 | 0 | response = res; |
493 | 0 | }; |
494 | | |
495 | | // 4. If performFetch was given, run performFetch with request, isTopLevel, and with processResponseConsumeBody as defined below. |
496 | 0 | if (perform_fetch) { |
497 | 0 | TRY(perform_fetch->function()(request, TopLevelModule::Yes, move(process_response_consume_body))); |
498 | 0 | } |
499 | | // Otherwise, fetch request with processResponseConsumeBody set to processResponseConsumeBody as defined below. |
500 | 0 | else { |
501 | 0 | Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; |
502 | 0 | fetch_algorithms_input.process_response_consume_body = move(process_response_consume_body); |
503 | 0 | TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); |
504 | 0 | } |
505 | | |
506 | | // 5. Pause until response is not null. |
507 | 0 | auto& event_loop = settings_object.responsible_event_loop(); |
508 | 0 | event_loop.spin_until([&]() { |
509 | 0 | return response; |
510 | 0 | }); |
511 | | |
512 | | // 6. Set response to response's unsafe response. |
513 | 0 | response = response->unsafe_response(); |
514 | | |
515 | | // 7. If any of the following are true: |
516 | | // - bodyBytes is null or failure; |
517 | | // - response's status is not an ok status; or |
518 | | // - the result of extracting a MIME type from response's header list is not a JavaScript MIME type, |
519 | | // then throw a "NetworkError" DOMException. |
520 | 0 | if (body_bytes.template has<Empty>() || body_bytes.template has<Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag>() |
521 | 0 | || !Fetch::Infrastructure::is_ok_status(response->status()) |
522 | 0 | || !response->header_list()->extract_mime_type().has_value() || !response->header_list()->extract_mime_type()->is_javascript()) { |
523 | 0 | return WebIDL::NetworkError::create(realm, "Network error"_string); |
524 | 0 | } |
525 | | |
526 | | // 8. Let sourceText be the result of UTF-8 decoding bodyBytes. |
527 | 0 | auto decoder = TextCodec::decoder_for("UTF-8"sv); |
528 | 0 | VERIFY(decoder.has_value()); |
529 | 0 | auto source_text = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, body_bytes.get<ByteBuffer>()).release_value_but_fixme_should_propagate_errors(); |
530 | | |
531 | | // 9. Let mutedErrors be true if response was CORS-cross-origin, and false otherwise. |
532 | 0 | auto muted_errors = response->is_cors_cross_origin() ? ClassicScript::MutedErrors::Yes : ClassicScript::MutedErrors::No; |
533 | | |
534 | | // 10. Let script be the result of creating a classic script given sourceText, settingsObject, response's URL, the default classic script fetch options, and mutedErrors. |
535 | 0 | auto response_url = response->url().value_or({}); |
536 | 0 | auto script = ClassicScript::create(response_url.to_byte_string(), source_text, settings_object, response_url, 1, muted_errors); |
537 | | |
538 | | // 11. Return script. |
539 | 0 | return script; |
540 | 0 | } |
541 | | |
542 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-worker-script-tree |
543 | | WebIDL::ExceptionOr<void> fetch_module_worker_script_graph(URL::URL const& url, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook perform_fetch, OnFetchScriptComplete on_complete) |
544 | 0 | { |
545 | 0 | return fetch_worklet_module_worker_script_graph(url, fetch_client, destination, settings_object, move(perform_fetch), move(on_complete)); |
546 | 0 | } |
547 | | |
548 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-worklet/module-worker-script-graph |
549 | | WebIDL::ExceptionOr<void> fetch_worklet_module_worker_script_graph(URL::URL const& url, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook perform_fetch, OnFetchScriptComplete on_complete) |
550 | 0 | { |
551 | 0 | auto& realm = settings_object.realm(); |
552 | 0 | auto& vm = realm.vm(); |
553 | | |
554 | | // 1. Let options be a script fetch options whose cryptographic nonce is the empty string, |
555 | | // integrity metadata is the empty string, parser metadata is "not-parser-inserted", |
556 | | // credentials mode is credentialsMode, referrer policy is the empty string, and fetch priority is "auto". |
557 | | // FIXME: credentialsMode |
558 | 0 | auto options = ScriptFetchOptions { |
559 | 0 | .cryptographic_nonce = String {}, |
560 | 0 | .integrity_metadata = String {}, |
561 | 0 | .parser_metadata = Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted, |
562 | 0 | .credentials_mode = Fetch::Infrastructure::Request::CredentialsMode::SameOrigin, |
563 | 0 | .referrer_policy = ReferrerPolicy::ReferrerPolicy::EmptyString, |
564 | 0 | .fetch_priority = Fetch::Infrastructure::Request::Priority::Auto |
565 | 0 | }; |
566 | | |
567 | | // onSingleFetchComplete given result is the following algorithm: |
568 | 0 | auto on_single_fetch_complete = create_on_fetch_script_complete(vm.heap(), [&realm, &fetch_client, destination, perform_fetch = perform_fetch, on_complete = move(on_complete)](auto result) mutable { |
569 | | // 1. If result is null, run onComplete with null, and abort these steps. |
570 | 0 | if (!result) { |
571 | 0 | dbgln("on single fetch complete with nool"); |
572 | 0 | on_complete->function()(nullptr); |
573 | 0 | return; |
574 | 0 | } |
575 | | |
576 | | // 2. Fetch the descendants of and link result given fetchClient, destination, and onComplete. If performFetch was given, pass it along as well. |
577 | 0 | fetch_descendants_of_and_link_a_module_script(realm, verify_cast<JavaScriptModuleScript>(*result), fetch_client, destination, move(perform_fetch), on_complete); |
578 | 0 | }); |
579 | | |
580 | | // 2. Fetch a single module script given url, fetchClient, destination, options, settingsObject, "client", true, |
581 | | // and onSingleFetchComplete as defined below. If performFetch was given, pass it along as well. |
582 | 0 | fetch_single_module_script(realm, url, fetch_client, destination, options, settings_object, Fetch::Infrastructure::Request::Referrer::Client, {}, TopLevelModule::Yes, move(perform_fetch), on_single_fetch_complete); |
583 | |
|
584 | 0 | return {}; |
585 | 0 | } |
586 | | |
587 | | // https://html.spec.whatwg.org/multipage/webappapis.html#internal-module-script-graph-fetching-procedure |
588 | | void fetch_internal_module_script_graph(JS::Realm& realm, JS::ModuleRequest const& module_request, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination destination, ScriptFetchOptions const& options, Script& referring_script, HashTable<ModuleLocationTuple> const& visited_set, PerformTheFetchHook perform_fetch, OnFetchScriptComplete on_complete) |
589 | 0 | { |
590 | | // 1. Let url be the result of resolving a module specifier given referringScript and moduleRequest.[[Specifier]]. |
591 | 0 | auto url = MUST(resolve_module_specifier(referring_script, module_request.module_specifier)); |
592 | | |
593 | | // 2. Assert: the previous step never throws an exception, because resolving a module specifier must have been previously successful with these same two arguments. |
594 | | // NOTE: Handled by MUST above. |
595 | | |
596 | | // 3. Let moduleType be the result of running the module type from module request steps given moduleRequest. |
597 | 0 | auto module_type = module_type_from_module_request(module_request); |
598 | | |
599 | | // 4. Assert: visited set contains (url, moduleType). |
600 | 0 | VERIFY(visited_set.contains({ url, module_type })); |
601 | | |
602 | | // onSingleFetchComplete given result is the following algorithm: |
603 | 0 | auto on_single_fetch_complete = create_on_fetch_script_complete(realm.heap(), [&realm, perform_fetch, on_complete, &fetch_client_settings_object, destination, visited_set](auto result) mutable { |
604 | | // 1. If result is null, run onComplete with null, and abort these steps. |
605 | 0 | if (!result) { |
606 | 0 | on_complete->function()(nullptr); |
607 | 0 | return; |
608 | 0 | } |
609 | | |
610 | | // 2. Fetch the descendants of result given fetch client settings object, destination, visited set, and with onComplete. If performFetch was given, pass it along as well. |
611 | 0 | auto& module_script = verify_cast<JavaScriptModuleScript>(*result); |
612 | 0 | fetch_descendants_of_a_module_script(realm, module_script, fetch_client_settings_object, destination, visited_set, perform_fetch, on_complete); |
613 | 0 | }); |
614 | | |
615 | | // 5. Fetch a single module script given url, fetch client settings object, destination, options, referringScript's settings object, |
616 | | // referringScript's base URL, moduleRequest, false, and onSingleFetchComplete as defined below. If performFetch was given, pass it along as well. |
617 | 0 | fetch_single_module_script(realm, url, fetch_client_settings_object, destination, options, referring_script.settings_object(), referring_script.base_url(), module_request, TopLevelModule::No, perform_fetch, on_single_fetch_complete); |
618 | 0 | } |
619 | | |
620 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-a-module-script |
621 | | void fetch_descendants_of_a_module_script(JS::Realm& realm, JavaScriptModuleScript& module_script, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination destination, HashTable<ModuleLocationTuple> visited_set, PerformTheFetchHook perform_fetch, OnFetchScriptComplete on_complete) |
622 | 0 | { |
623 | | // 1. If module script's record is null, run onComplete with module script and return. |
624 | 0 | if (!module_script.record()) { |
625 | 0 | on_complete->function()(&module_script); |
626 | 0 | return; |
627 | 0 | } |
628 | | |
629 | | // 2. Let record be module script's record. |
630 | 0 | auto const& record = module_script.record(); |
631 | | |
632 | | // 3. If record is not a Cyclic Module Record, or if record.[[RequestedModules]] is empty, run onComplete with module script and return. |
633 | | // FIXME: Currently record is always a cyclic module. |
634 | 0 | if (record->requested_modules().is_empty()) { |
635 | 0 | on_complete->function()(&module_script); |
636 | 0 | return; |
637 | 0 | } |
638 | | |
639 | | // 4. Let moduleRequests be a new empty list. |
640 | 0 | Vector<JS::ModuleRequest> module_requests; |
641 | | |
642 | | // 5. For each ModuleRequest Record requested of record.[[RequestedModules]], |
643 | 0 | for (auto const& requested : record->requested_modules()) { |
644 | | // 1. Let url be the result of resolving a module specifier given module script and requested.[[Specifier]]. |
645 | 0 | auto url = MUST(resolve_module_specifier(module_script, requested.module_specifier)); |
646 | | |
647 | | // 2. Assert: the previous step never throws an exception, because resolving a module specifier must have been previously successful with these same two arguments. |
648 | | // NOTE: Handled by MUST above. |
649 | | |
650 | | // 3. Let moduleType be the result of running the module type from module request steps given requested. |
651 | 0 | auto module_type = module_type_from_module_request(requested); |
652 | | |
653 | | // 4. If visited set does not contain (url, moduleType), then: |
654 | 0 | if (!visited_set.contains({ url, module_type })) { |
655 | | // 1. Append requested to moduleRequests. |
656 | 0 | module_requests.append(requested); |
657 | | |
658 | | // 2. Append (url, moduleType) to visited set. |
659 | 0 | visited_set.set({ url, module_type }); |
660 | 0 | } |
661 | 0 | } |
662 | | |
663 | | // FIXME: 6. Let options be the descendant script fetch options for module script's fetch options. |
664 | 0 | ScriptFetchOptions options; |
665 | | |
666 | | // FIXME: 7. Assert: options is not null, as module script is a JavaScript module script. |
667 | | |
668 | | // 8. Let pendingCount be the length of moduleRequests. |
669 | 0 | auto pending_count = module_requests.size(); |
670 | | |
671 | | // 9. If pendingCount is zero, run onComplete with module script. |
672 | 0 | if (pending_count == 0) { |
673 | 0 | on_complete->function()(&module_script); |
674 | 0 | return; |
675 | 0 | } |
676 | | |
677 | | // 10. Let failed be false. |
678 | 0 | bool failed = false; |
679 | | |
680 | | // 11. For each moduleRequest in moduleRequests, perform the internal module script graph fetching procedure given moduleRequest, |
681 | | // fetch client settings object, destination, options, module script, visited set, and onInternalFetchingComplete as defined below. |
682 | | // If performFetch was given, pass it along as well. |
683 | 0 | for (auto const& module_request : module_requests) { |
684 | | // onInternalFetchingComplete given result is the following algorithm: |
685 | 0 | auto on_internal_fetching_complete = create_on_fetch_script_complete(realm.heap(), [failed, pending_count, &module_script, on_complete](auto result) mutable { |
686 | | // 1. If failed is true, then abort these steps. |
687 | 0 | if (failed) |
688 | 0 | return; |
689 | | |
690 | | // 2. If result is null, then set failed to true, run onComplete with null, and abort these steps. |
691 | 0 | if (!result) { |
692 | 0 | failed = true; |
693 | 0 | on_complete->function()(nullptr); |
694 | 0 | return; |
695 | 0 | } |
696 | | |
697 | | // 3. Assert: pendingCount is greater than zero. |
698 | 0 | VERIFY(pending_count > 0); |
699 | | |
700 | | // 4. Decrement pendingCount by one. |
701 | 0 | --pending_count; |
702 | | |
703 | | // 5. If pendingCount is zero, run onComplete with module script. |
704 | 0 | if (pending_count == 0) |
705 | 0 | on_complete->function()(&module_script); |
706 | 0 | }); |
707 | |
|
708 | 0 | fetch_internal_module_script_graph(realm, module_request, fetch_client_settings_object, destination, options, module_script, visited_set, perform_fetch, on_internal_fetching_complete); |
709 | 0 | } |
710 | 0 | } |
711 | | |
712 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-destination-from-module-type |
713 | | Fetch::Infrastructure::Request::Destination fetch_destination_from_module_type(Fetch::Infrastructure::Request::Destination default_destination, ByteString const& module_type) |
714 | 0 | { |
715 | | // 1. If moduleType is "json", then return "json". |
716 | 0 | if (module_type == "json"sv) |
717 | 0 | return Fetch::Infrastructure::Request::Destination::JSON; |
718 | | |
719 | | // 2. If moduleType is "css", then return "style". |
720 | 0 | if (module_type == "css"sv) |
721 | 0 | return Fetch::Infrastructure::Request::Destination::Style; |
722 | | |
723 | | // 3. Return defaultDestination. |
724 | 0 | return default_destination; |
725 | 0 | } |
726 | | |
727 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-module-script |
728 | | void fetch_single_module_script(JS::Realm& realm, |
729 | | URL::URL const& url, |
730 | | EnvironmentSettingsObject& fetch_client, |
731 | | Fetch::Infrastructure::Request::Destination destination, |
732 | | ScriptFetchOptions const& options, |
733 | | EnvironmentSettingsObject& settings_object, |
734 | | Web::Fetch::Infrastructure::Request::ReferrerType const& referrer, |
735 | | Optional<JS::ModuleRequest> const& module_request, |
736 | | TopLevelModule is_top_level, |
737 | | PerformTheFetchHook perform_fetch, |
738 | | OnFetchScriptComplete on_complete) |
739 | 0 | { |
740 | | // 1. Let moduleType be "javascript". |
741 | 0 | ByteString module_type = "javascript"sv; |
742 | | |
743 | | // 2. If moduleRequest was given, then set moduleType to the result of running the module type from module request steps given moduleRequest. |
744 | 0 | if (module_request.has_value()) |
745 | 0 | module_type = module_type_from_module_request(*module_request); |
746 | | |
747 | | // 3. Assert: the result of running the module type allowed steps given moduleType and settingsObject is true. |
748 | | // Otherwise we would not have reached this point because a failure would have been raised when inspecting moduleRequest.[[Assertions]] |
749 | | // in create a JavaScript module script or fetch a single imported module script. |
750 | 0 | VERIFY(settings_object.module_type_allowed(module_type)); |
751 | | |
752 | | // 4. Let moduleMap be settingsObject's module map. |
753 | 0 | auto& module_map = settings_object.module_map(); |
754 | | |
755 | | // 5. If moduleMap[(url, moduleType)] is "fetching", wait in parallel until that entry's value changes, |
756 | | // then queue a task on the networking task source to proceed with running the following steps. |
757 | 0 | if (module_map.is_fetching(url, module_type)) { |
758 | 0 | module_map.wait_for_change(realm.heap(), url, module_type, [on_complete, &realm](auto entry) -> void { |
759 | 0 | HTML::queue_global_task(HTML::Task::Source::Networking, realm.global_object(), JS::create_heap_function(realm.heap(), [on_complete, entry] { |
760 | | // FIXME: This should run other steps, for now we just assume the script loaded. |
761 | 0 | VERIFY(entry.type == ModuleMap::EntryType::ModuleScript || entry.type == ModuleMap::EntryType::Failed); |
762 | | |
763 | 0 | on_complete->function()(entry.module_script); |
764 | 0 | })); |
765 | 0 | }); |
766 | |
|
767 | 0 | return; |
768 | 0 | } |
769 | | |
770 | | // 6. If moduleMap[(url, moduleType)] exists, run onComplete given moduleMap[(url, moduleType)], and return. |
771 | 0 | auto entry = module_map.get(url, module_type); |
772 | 0 | if (entry.has_value() && entry->type == ModuleMap::EntryType::ModuleScript) { |
773 | 0 | on_complete->function()(entry->module_script); |
774 | 0 | return; |
775 | 0 | } |
776 | | |
777 | | // 7. Set moduleMap[(url, moduleType)] to "fetching". |
778 | 0 | module_map.set(url, module_type, { ModuleMap::EntryType::Fetching, nullptr }); |
779 | | |
780 | | // 8. Let request be a new request whose URL is url, mode is "cors", referrer is referrer, and client is fetchClient. |
781 | 0 | auto request = Fetch::Infrastructure::Request::create(realm.vm()); |
782 | 0 | request->set_url(url); |
783 | 0 | request->set_mode(Fetch::Infrastructure::Request::Mode::CORS); |
784 | 0 | request->set_referrer(referrer); |
785 | 0 | request->set_client(&fetch_client); |
786 | | |
787 | | // 9. Set request's destination to the result of running the fetch destination from module type steps given destination and moduleType. |
788 | 0 | request->set_destination(fetch_destination_from_module_type(destination, module_type)); |
789 | | |
790 | | // 10. If destination is "worker", "sharedworker", or "serviceworker", and isTopLevel is true, then set request's mode to "same-origin". |
791 | 0 | if ((destination == Fetch::Infrastructure::Request::Destination::Worker || destination == Fetch::Infrastructure::Request::Destination::SharedWorker || destination == Fetch::Infrastructure::Request::Destination::ServiceWorker) && is_top_level == TopLevelModule::Yes) |
792 | 0 | request->set_mode(Fetch::Infrastructure::Request::Mode::SameOrigin); |
793 | | |
794 | | // 11. Set request's initiator type to "script". |
795 | 0 | request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Script); |
796 | | |
797 | | // 12. Set up the module script request given request and options. |
798 | 0 | set_up_module_script_request(request, options); |
799 | | |
800 | | // 13. If performFetch was given, run performFetch with request, isTopLevel, and with processResponseConsumeBody as defined below. |
801 | | // Otherwise, fetch request with processResponseConsumeBody set to processResponseConsumeBody as defined below. |
802 | | // In both cases, let processResponseConsumeBody given response response and null, failure, or a byte sequence bodyBytes be the following algorithm: |
803 | 0 | auto process_response_consume_body = [&module_map, url, module_type, &settings_object, on_complete](JS::NonnullGCPtr<Fetch::Infrastructure::Response> response, Fetch::Infrastructure::FetchAlgorithms::BodyBytes body_bytes) { |
804 | | // 1. If either of the following conditions are met: |
805 | | // - bodyBytes is null or failure; or |
806 | | // - response's status is not an ok status, |
807 | 0 | if (body_bytes.has<Empty>() || body_bytes.has<Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag>() || !Fetch::Infrastructure::is_ok_status(response->status())) { |
808 | | // then set moduleMap[(url, moduleType)] to null, run onComplete given null, and abort these steps. |
809 | 0 | module_map.set(url, module_type, { ModuleMap::EntryType::Failed, nullptr }); |
810 | 0 | on_complete->function()(nullptr); |
811 | 0 | return; |
812 | 0 | } |
813 | | |
814 | | // 2. Let sourceText be the result of UTF-8 decoding bodyBytes. |
815 | 0 | auto decoder = TextCodec::decoder_for("UTF-8"sv); |
816 | 0 | VERIFY(decoder.has_value()); |
817 | 0 | auto source_text = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, body_bytes.get<ByteBuffer>()).release_value_but_fixme_should_propagate_errors(); |
818 | | |
819 | | // 3. Let mimeType be the result of extracting a MIME type from response's header list. |
820 | 0 | auto mime_type = response->header_list()->extract_mime_type(); |
821 | | |
822 | | // 4. Let moduleScript be null. |
823 | 0 | JS::GCPtr<JavaScriptModuleScript> module_script; |
824 | | |
825 | | // FIXME: 5. Let referrerPolicy be the result of parsing the `Referrer-Policy` header given response. [REFERRERPOLICY] |
826 | | // FIXME: 6. If referrerPolicy is not the empty string, set options's referrer policy to referrerPolicy. |
827 | | |
828 | | // 7. If mimeType is a JavaScript MIME type and moduleType is "javascript", then set moduleScript to the result of creating a JavaScript module script given sourceText, settingsObject, response's URL, and options. |
829 | | // FIXME: Pass options. |
830 | 0 | if (mime_type->is_javascript() && module_type == "javascript") |
831 | 0 | module_script = JavaScriptModuleScript::create(url.basename(), source_text, settings_object, response->url().value_or({})).release_value_but_fixme_should_propagate_errors(); |
832 | | |
833 | | // FIXME: 8. If the MIME type essence of mimeType is "text/css" and moduleType is "css", then set moduleScript to the result of creating a CSS module script given sourceText and settingsObject. |
834 | | // FIXME: 9. If mimeType is a JSON MIME type and moduleType is "json", then set moduleScript to the result of creating a JSON module script given sourceText and settingsObject. |
835 | | |
836 | | // 10. Set moduleMap[(url, moduleType)] to moduleScript, and run onComplete given moduleScript. |
837 | 0 | module_map.set(url, module_type, { ModuleMap::EntryType::ModuleScript, module_script }); |
838 | 0 | on_complete->function()(module_script); |
839 | 0 | }; |
840 | |
|
841 | 0 | if (perform_fetch != nullptr) { |
842 | 0 | perform_fetch->function()(request, is_top_level, move(process_response_consume_body)).release_value_but_fixme_should_propagate_errors(); |
843 | 0 | } else { |
844 | 0 | Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; |
845 | 0 | fetch_algorithms_input.process_response_consume_body = move(process_response_consume_body); |
846 | 0 | Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(realm.vm(), move(fetch_algorithms_input))).release_value_but_fixme_should_propagate_errors(); |
847 | 0 | } |
848 | 0 | } |
849 | | |
850 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree |
851 | | void fetch_external_module_script_graph(JS::Realm& realm, URL::URL const& url, EnvironmentSettingsObject& settings_object, ScriptFetchOptions const& options, OnFetchScriptComplete on_complete) |
852 | 0 | { |
853 | | // 1. Disallow further import maps given settingsObject. |
854 | 0 | settings_object.disallow_further_import_maps(); |
855 | |
|
856 | 0 | auto steps = create_on_fetch_script_complete(realm.heap(), [&realm, &settings_object, on_complete, url](auto result) mutable { |
857 | | // 1. If result is null, run onComplete given null, and abort these steps. |
858 | 0 | if (!result) { |
859 | 0 | on_complete->function()(nullptr); |
860 | 0 | return; |
861 | 0 | } |
862 | | |
863 | | // 2. Fetch the descendants of and link result given settingsObject, "script", and onComplete. |
864 | 0 | auto& module_script = verify_cast<JavaScriptModuleScript>(*result); |
865 | 0 | fetch_descendants_of_and_link_a_module_script(realm, module_script, settings_object, Fetch::Infrastructure::Request::Destination::Script, nullptr, on_complete); |
866 | 0 | }); |
867 | | |
868 | | // 2. Fetch a single module script given url, settingsObject, "script", options, settingsObject, "client", true, and with the following steps given result: |
869 | 0 | fetch_single_module_script(realm, url, settings_object, Fetch::Infrastructure::Request::Destination::Script, options, settings_object, Web::Fetch::Infrastructure::Request::Referrer::Client, {}, TopLevelModule::Yes, nullptr, steps); |
870 | 0 | } |
871 | | |
872 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph |
873 | | void fetch_inline_module_script_graph(JS::Realm& realm, ByteString const& filename, ByteString const& source_text, URL::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete) |
874 | 0 | { |
875 | | // 1. Disallow further import maps given settingsObject. |
876 | 0 | settings_object.disallow_further_import_maps(); |
877 | | |
878 | | // 2. Let script be the result of creating a JavaScript module script using sourceText, settingsObject, baseURL, and options. |
879 | 0 | auto script = JavaScriptModuleScript::create(filename, source_text.view(), settings_object, base_url).release_value_but_fixme_should_propagate_errors(); |
880 | | |
881 | | // 3. If script is null, run onComplete given null, and return. |
882 | 0 | if (!script) { |
883 | 0 | on_complete->function()(nullptr); |
884 | 0 | return; |
885 | 0 | } |
886 | | |
887 | | // 5. Fetch the descendants of and link script, given settingsObject, "script", and onComplete. |
888 | 0 | fetch_descendants_of_and_link_a_module_script(realm, *script, settings_object, Fetch::Infrastructure::Request::Destination::Script, nullptr, on_complete); |
889 | 0 | } |
890 | | |
891 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-imported-module-script |
892 | | void fetch_single_imported_module_script(JS::Realm& realm, |
893 | | URL::URL const& url, |
894 | | EnvironmentSettingsObject& fetch_client, |
895 | | Fetch::Infrastructure::Request::Destination destination, |
896 | | ScriptFetchOptions const& options, |
897 | | EnvironmentSettingsObject& settings_object, |
898 | | Fetch::Infrastructure::Request::ReferrerType referrer, |
899 | | JS::ModuleRequest const& module_request, |
900 | | PerformTheFetchHook perform_fetch, |
901 | | OnFetchScriptComplete on_complete) |
902 | 0 | { |
903 | | // 1. Assert: moduleRequest.[[Attributes]] does not contain any Record entry such that entry.[[Key]] is not "type", |
904 | | // because we only asked for "type" attributes in HostGetSupportedImportAttributes. |
905 | 0 | for (auto const& entry : module_request.attributes) |
906 | 0 | VERIFY(entry.key == "type"sv); |
907 | | |
908 | | // 2. Let moduleType be the result of running the module type from module request steps given moduleRequest. |
909 | 0 | auto module_type = module_type_from_module_request(module_request); |
910 | | |
911 | | // 3. If the result of running the module type allowed steps given moduleType and settingsObject is false, |
912 | | // then run onComplete given null, and return. |
913 | 0 | if (!settings_object.module_type_allowed(module_type)) { |
914 | 0 | on_complete->function()(nullptr); |
915 | 0 | return; |
916 | 0 | } |
917 | | |
918 | | // 4. Fetch a single module script given url, fetchClient, destination, options, settingsObject, referrer, moduleRequest, false, |
919 | | // and onComplete. If performFetch was given, pass it along as well. |
920 | 0 | fetch_single_module_script(realm, url, fetch_client, destination, options, settings_object, referrer, module_request, TopLevelModule::No, perform_fetch, on_complete); |
921 | 0 | } |
922 | | |
923 | | // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-and-link-a-module-script |
924 | | void fetch_descendants_of_and_link_a_module_script(JS::Realm& realm, |
925 | | JavaScriptModuleScript& module_script, |
926 | | EnvironmentSettingsObject& fetch_client, |
927 | | Fetch::Infrastructure::Request::Destination destination, |
928 | | PerformTheFetchHook perform_fetch, |
929 | | OnFetchScriptComplete on_complete) |
930 | 0 | { |
931 | | // 1. Let record be moduleScript's record. |
932 | 0 | auto* record = module_script.record(); |
933 | | |
934 | | // 2. If record is null, then: |
935 | 0 | if (!record) { |
936 | | // 1. Set moduleScript's error to rethrow to moduleScript's parse error. |
937 | 0 | module_script.set_error_to_rethrow(module_script.parse_error()); |
938 | | |
939 | | // 2. Run onComplete given moduleScript. |
940 | 0 | on_complete->function()(module_script); |
941 | | |
942 | | // 3. Return. |
943 | 0 | return; |
944 | 0 | } |
945 | | |
946 | | // 3. Let state be Record { [[ParseError]]: null, [[Destination]]: destination, [[PerformFetch]]: null, [[FetchClient]]: fetchClient }. |
947 | 0 | auto state = realm.heap().allocate_without_realm<FetchContext>(JS::js_null(), destination, nullptr, fetch_client); |
948 | | |
949 | | // 4. If performFetch was given, set state.[[PerformFetch]] to performFetch. |
950 | 0 | state->perform_fetch = perform_fetch; |
951 | | |
952 | | // FIXME: These should most likely be steps in the spec. |
953 | | // NOTE: For reasons beyond my understanding, we cannot use TemporaryExecutionContext here. |
954 | | // Calling perform_a_microtask_checkpoint() on the fetch_client's responsible_event_loop |
955 | | // prevents this from functioning properly. HTMLParser::the_end would be run before |
956 | | // HTMLScriptElement::prepare_script had a chance to setup the callback to mark_done properly, |
957 | | // resulting in the event loop hanging forever awaiting for the script to be ready for parser |
958 | | // execution. |
959 | 0 | realm.vm().push_execution_context(fetch_client.realm_execution_context()); |
960 | 0 | fetch_client.prepare_to_run_callback(); |
961 | | |
962 | | // 5. Let loadingPromise be record.LoadRequestedModules(state). |
963 | 0 | auto& loading_promise = record->load_requested_modules(state); |
964 | | |
965 | | // 6. Upon fulfillment of loadingPromise, run the following steps: |
966 | 0 | WebIDL::upon_fulfillment(loading_promise, JS::create_heap_function(realm.heap(), [&realm, record, &module_script, on_complete](JS::Value) -> WebIDL::ExceptionOr<JS::Value> { |
967 | | // 1. Perform record.Link(). |
968 | 0 | auto linking_result = record->link(realm.vm()); |
969 | | |
970 | | // If this throws an exception, set result's error to rethrow to that exception. |
971 | 0 | if (linking_result.is_throw_completion()) |
972 | 0 | module_script.set_error_to_rethrow(linking_result.release_error().value().value()); |
973 | | |
974 | | // 2. Run onComplete given moduleScript. |
975 | 0 | on_complete->function()(module_script); |
976 | |
|
977 | 0 | return JS::js_undefined(); |
978 | 0 | })); |
979 | | |
980 | | // 7. Upon rejection of loadingPromise, run the following steps: |
981 | 0 | WebIDL::upon_rejection(loading_promise, JS::create_heap_function(realm.heap(), [state, &module_script, on_complete](JS::Value) -> WebIDL::ExceptionOr<JS::Value> { |
982 | | // 1. If state.[[ParseError]] is not null, set moduleScript's error to rethrow to state.[[ParseError]] and run |
983 | | // onComplete given moduleScript. |
984 | 0 | if (!state->parse_error.is_null()) { |
985 | 0 | module_script.set_error_to_rethrow(state->parse_error); |
986 | |
|
987 | 0 | on_complete->function()(module_script); |
988 | 0 | } |
989 | | // 2. Otherwise, run onComplete given null. |
990 | 0 | else { |
991 | 0 | on_complete->function()(nullptr); |
992 | 0 | } |
993 | |
|
994 | 0 | return JS::js_undefined(); |
995 | 0 | })); |
996 | |
|
997 | 0 | fetch_client.clean_up_after_running_callback(); |
998 | 0 | realm.vm().pop_execution_context(); |
999 | 0 | } |
1000 | | |
1001 | | } |