diff --git a/Tests/LibWeb/Text/expected/Worker/Worker-importScripts.txt b/Tests/LibWeb/Text/expected/Worker/Worker-importScripts.txt
new file mode 100644
index 00000000000..cf32c9d3624
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/Worker/Worker-importScripts.txt
@@ -0,0 +1 @@
+importScripts() works as expected: YES
diff --git a/Tests/LibWeb/Text/input/Worker/Worker-importScripts.html b/Tests/LibWeb/Text/input/Worker/Worker-importScripts.html
new file mode 100644
index 00000000000..80403b87b06
--- /dev/null
+++ b/Tests/LibWeb/Text/input/Worker/Worker-importScripts.html
@@ -0,0 +1,10 @@
+
+
diff --git a/Tests/LibWeb/Text/input/Worker/worker-importScripts-scriptToImport.js b/Tests/LibWeb/Text/input/Worker/worker-importScripts-scriptToImport.js
new file mode 100644
index 00000000000..8d6e77a857b
--- /dev/null
+++ b/Tests/LibWeb/Text/input/Worker/worker-importScripts-scriptToImport.js
@@ -0,0 +1,3 @@
+function importedFunction() {
+ return "YES";
+}
diff --git a/Tests/LibWeb/Text/input/Worker/worker-importScripts.js b/Tests/LibWeb/Text/input/Worker/worker-importScripts.js
new file mode 100644
index 00000000000..677b777c5e9
--- /dev/null
+++ b/Tests/LibWeb/Text/input/Worker/worker-importScripts.js
@@ -0,0 +1,3 @@
+importScripts("worker-importScripts-scriptToImport.js");
+const fromImportedScript = importedFunction();
+self.postMessage(fromImportedScript);
diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp
index 1bfe7abb10d..c7222789f88 100644
--- a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp
@@ -1,9 +1,11 @@
/*
* Copyright (c) 2022-2023, networkException
+ * Copyright (c) 2024, Tim Ledbetter
*
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include
#include
#include
#include
@@ -423,6 +425,83 @@ WebIDL::ExceptionOr fetch_classic_worker_script(URL::URL const& url, Envir
return {};
}
+// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-worker-imported-script
+WebIDL::ExceptionOr> fetch_a_classic_worker_imported_script(URL::URL const& url, HTML::EnvironmentSettingsObject& settings_object, PerformTheFetchHook perform_fetch)
+{
+ auto& realm = settings_object.realm();
+ auto& vm = realm.vm();
+
+ // 1. Let response be null.
+ JS::GCPtr response = nullptr;
+
+ // 2. Let bodyBytes be null.
+ Fetch::Infrastructure::FetchAlgorithms::BodyBytes body_bytes;
+
+ // 3. Let request be a new request whose URL is url, client is settingsObject, destination is "script", initiator type is "other",
+ // parser metadata is "not parser-inserted", and whose use-URL-credentials flag is set.
+ auto request = Fetch::Infrastructure::Request::create(vm);
+ request->set_url(url);
+ request->set_client(&settings_object);
+ request->set_destination(Fetch::Infrastructure::Request::Destination::Script);
+ request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Other);
+ request->set_parser_metadata(Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted);
+ request->set_use_url_credentials(true);
+
+ auto process_response_consume_body = [&response, &body_bytes](JS::NonnullGCPtr res, Fetch::Infrastructure::FetchAlgorithms::BodyBytes bb) {
+ // 1. Set bodyBytes to bb.
+ body_bytes = move(bb);
+
+ // 2. Set response to res.
+ response = res;
+ };
+
+ // 4. If performFetch was given, run performFetch with request, isTopLevel, and with processResponseConsumeBody as defined below.
+ if (perform_fetch) {
+ TRY(perform_fetch->function()(request, TopLevelModule::Yes, move(process_response_consume_body)));
+ }
+ // Otherwise, fetch request with processResponseConsumeBody set to processResponseConsumeBody as defined below.
+ else {
+ Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
+ fetch_algorithms_input.process_response_consume_body = move(process_response_consume_body);
+ TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))));
+ }
+
+ // 5. Pause until response is not null.
+ auto& event_loop = settings_object.responsible_event_loop();
+ event_loop.spin_until([&]() {
+ return response;
+ });
+
+ // 6. Set response to response's unsafe response.
+ response = response->unsafe_response();
+
+ // 7. If any of the following are true:
+ // - bodyBytes is null or failure;
+ // - response's status is not an ok status; or
+ // - the result of extracting a MIME type from response's header list is not a JavaScript MIME type,
+ // then throw a "NetworkError" DOMException.
+ if (body_bytes.template has() || body_bytes.template has()
+ || !Fetch::Infrastructure::is_ok_status(response->status())
+ || !response->header_list()->extract_mime_type().has_value() || !response->header_list()->extract_mime_type()->is_javascript()) {
+ return WebIDL::NetworkError::create(realm, "Network error"_fly_string);
+ }
+
+ // 8. Let sourceText be the result of UTF-8 decoding bodyBytes.
+ auto decoder = TextCodec::decoder_for("UTF-8"sv);
+ VERIFY(decoder.has_value());
+ auto source_text = TextCodec::convert_input_to_utf8_using_given_decoder_unless_there_is_a_byte_order_mark(*decoder, body_bytes.get()).release_value_but_fixme_should_propagate_errors();
+
+ // 9. Let mutedErrors be true if response was CORS-cross-origin, and false otherwise.
+ auto muted_errors = response->is_cors_cross_origin() ? ClassicScript::MutedErrors::Yes : ClassicScript::MutedErrors::No;
+
+ // 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.
+ auto response_url = response->url().value_or({});
+ auto script = ClassicScript::create(response_url.to_byte_string(), source_text, settings_object, response_url, 1, muted_errors);
+
+ // 11. Return script.
+ return script;
+}
+
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-worker-script-tree
WebIDL::ExceptionOr 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)
{
diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h
index 8df74d475d5..a43cba57cfb 100644
--- a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h
+++ b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h
@@ -89,6 +89,7 @@ Optional resolve_url_like_module_specifier(ByteString const& specifier
WebIDL::ExceptionOr fetch_classic_script(JS::NonnullGCPtr, URL::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete);
WebIDL::ExceptionOr fetch_classic_worker_script(URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook, OnFetchScriptComplete);
+WebIDL::ExceptionOr> fetch_a_classic_worker_imported_script(URL::URL const&, HTML::EnvironmentSettingsObject&, PerformTheFetchHook = nullptr);
WebIDL::ExceptionOr fetch_module_worker_script_graph(URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook, OnFetchScriptComplete);
WebIDL::ExceptionOr fetch_worklet_module_worker_script_graph(URL::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, EnvironmentSettingsObject& settings_object, PerformTheFetchHook, OnFetchScriptComplete);
void fetch_internal_module_script_graph(JS::Realm&, JS::ModuleRequest const& module_request, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, Script& referring_script, HashTable const& visited_set, PerformTheFetchHook, OnFetchScriptComplete on_complete);
diff --git a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp
index 157424f4314..97b0333d873 100644
--- a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp
+++ b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -70,28 +71,51 @@ void WorkerGlobalScope::set_internal_port(JS::NonnullGCPtr port)
}
// https://html.spec.whatwg.org/multipage/workers.html#importing-scripts-and-libraries
-WebIDL::ExceptionOr WorkerGlobalScope::import_scripts(Vector urls)
+WebIDL::ExceptionOr WorkerGlobalScope::import_scripts(Vector const& urls, PerformTheFetchHook perform_fetch)
{
// The algorithm may optionally be customized by supplying custom perform the fetch hooks,
// which if provided will be used when invoking fetch a classic worker-imported script.
// NOTE: Service Workers is an example of a specification that runs this algorithm with its own options for the perform the fetch hook.
// FIXME: 1. If worker global scope's type is "module", throw a TypeError exception.
- // FIXME: 2. Let settings object be the current settings object.
+
+ // 2. Let settings object be the current settings object.
+ auto& settings_object = HTML::current_settings_object();
// 3. If urls is empty, return.
if (urls.is_empty())
return {};
- // FIXME: 4. Parse each value in urls relative to settings object. If any fail, throw a "SyntaxError" DOMException.
- // FIXME: 5. For each url in the resulting URL records, run these substeps:
- // 1. Fetch a classic worker-imported script given url and settings object, passing along any custom perform the fetch steps provided.
- // If this succeeds, let script be the result. Otherwise, rethrow the exception.
- // 2. Run the classic script script, with the rethrow errors argument set to true.
- // NOTE: script will run until it either returns, fails to parse, fails to catch an exception,
- // or gets prematurely aborted by the terminate a worker algorithm defined above.
- // If an exception was thrown or if the script was prematurely aborted, then abort all these steps,
- // letting the exception or aborting continue to be processed by the calling script.
+ // 4. Let urlRecords be « ».
+ Vector url_records;
+ url_records.ensure_capacity(urls.size());
+
+ // 5. For each url of urls:
+ for (auto const& url : urls) {
+ // 1. Let urlRecord be the result of encoding-parsing a URL given url, relative to settings object.
+ auto url_record = settings_object.parse_url(url);
+
+ // 2. If urlRecord is failure, then throw a "SyntaxError" DOMException.
+ if (!url_record.is_valid())
+ return WebIDL::SyntaxError::create(realm(), "Invalid URL"_fly_string);
+
+ // 3. Append urlRecord to urlRecords.
+ url_records.unchecked_append(url_record);
+ }
+
+ // 6. For each urlRecord of urlRecords:
+ for (auto const& url_record : url_records) {
+ // 1. Fetch a classic worker-imported script given urlRecord and settings object, passing along performFetch if provided.
+ // If this succeeds, let script be the result. Otherwise, rethrow the exception.
+ auto classic_script = TRY(HTML::fetch_a_classic_worker_imported_script(url_record, settings_object, perform_fetch));
+
+ // 2. Run the classic script script, with the rethrow errors argument set to true.
+ // NOTE: script will run until it either returns, fails to parse, fails to catch an exception,
+ // or gets prematurely aborted by the terminate a worker algorithm defined above.
+ // If an exception was thrown or if the script was prematurely aborted, then abort all these steps,
+ // letting the exception or aborting continue to be processed by the calling script.
+ TRY(classic_script->run(ClassicScript::RethrowErrors::Yes));
+ }
return {};
}
diff --git a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h
index 4f92f18cca2..65307d268fa 100644
--- a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h
+++ b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -66,7 +67,7 @@ public:
JS::NonnullGCPtr location() const;
JS::NonnullGCPtr navigator() const;
- WebIDL::ExceptionOr import_scripts(Vector urls);
+ WebIDL::ExceptionOr import_scripts(Vector const& urls, PerformTheFetchHook = nullptr);
#undef __ENUMERATE
#define __ENUMERATE(attribute_name, event_name) \