/* * Copyright (c) 2023, Andrew Kaster * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include namespace WebWorker { DedicatedWorkerHost::DedicatedWorkerHost(URL url, String type) : m_url(move(url)) , m_type(move(type)) { } DedicatedWorkerHost::~DedicatedWorkerHost() = default; // https://html.spec.whatwg.org/multipage/workers.html#run-a-worker // FIXME: Extract out into a helper for both shared and dedicated workers void DedicatedWorkerHost::run(JS::NonnullGCPtr page, Web::HTML::TransferDataHolder message_port_data) { bool const is_shared = false; // 7. Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations: auto realm_execution_context = Web::Bindings::create_a_new_javascript_realm( Web::Bindings::main_thread_vm(), [page](JS::Realm& realm) -> JS::Object* { // 7a. For the global object, if is shared is true, create a new SharedWorkerGlobalScope object. // 7b. Otherwise, create a new DedicatedWorkerGlobalScope object. // FIXME: Proper support for both SharedWorkerGlobalScope and DedicatedWorkerGlobalScope if (is_shared) TODO(); return Web::Bindings::main_thread_vm().heap().allocate_without_realm(realm, page); }, nullptr); // 8. Let worker global scope be the global object of realm execution context's Realm component. // NOTE: This is the DedicatedWorkerGlobalScope or SharedWorkerGlobalScope object created in the previous step. JS::NonnullGCPtr worker_global_scope = verify_cast(realm_execution_context->realm->global_object()); // 9. Set up a worker environment settings object with realm execution context, // outside settings, and unsafeWorkerCreationTime, and let inside settings be the result. auto inner_settings = Web::HTML::WorkerEnvironmentSettingsObject::setup(page, move(realm_execution_context)); auto& console_object = *inner_settings->realm().intrinsics().console_object(); m_console = adopt_ref(*new Web::HTML::WorkerDebugConsoleClient(console_object.console())); VERIFY(m_console); console_object.console().set_client(*m_console); // 10. Set worker global scope's name to the value of options's name member. // FIXME: name property requires the SharedWorkerGlobalScope or DedicatedWorkerGlobalScope child class to be used // 11. Append owner to worker global scope's owner set. // FIXME: support for 'owner' set on WorkerGlobalScope // 12. If is shared is true, then: if (is_shared) { // FIXME: Shared worker support // 1. Set worker global scope's constructor origin to outside settings's origin. // 2. Set worker global scope's constructor url to url. // 3. Set worker global scope's type to the value of options's type member. // 4. Set worker global scope's credentials to the value of options's credentials member. } // 13. Let destination be "sharedworker" if is shared is true, and "worker" otherwise. auto destination = is_shared ? Web::Fetch::Infrastructure::Request::Destination::SharedWorker : Web::Fetch::Infrastructure::Request::Destination::Worker; // In both cases, let performFetch be the following perform the fetch hook given request, isTopLevel and processCustomFetchResponse: auto perform_fetch_function = [inner_settings, worker_global_scope](JS::NonnullGCPtr request, Web::HTML::IsTopLevel is_top_level, Web::Fetch::Infrastructure::FetchAlgorithms::ProcessResponseConsumeBodyFunction process_custom_fetch_response) -> Web::WebIDL::ExceptionOr { auto& realm = inner_settings->realm(); auto& vm = realm.vm(); Web::Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {}; // 1. If isTopLevel is false, fetch request with processResponseConsumeBody set to processCustomFetchResponse, and abort these steps. if (is_top_level == Web::HTML::IsTopLevel::No) { fetch_algorithms_input.process_response_consume_body = move(process_custom_fetch_response); TRY(Web::Fetch::Fetching::fetch(realm, request, Web::Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); return {}; } // 2. Set request's reserved client to inside settings. request->set_reserved_client(JS::GCPtr(inner_settings)); // We need to store the process custom fetch response function on the heap here, because we're storing it in another heap function auto process_custom_fetch_response_function = JS::create_heap_function(vm.heap(), move(process_custom_fetch_response)); // 3. Fetch request with processResponseConsumeBody set to the following steps given response response and null, failure, or a byte sequence bodyBytes: fetch_algorithms_input.process_response_consume_body = [worker_global_scope, process_custom_fetch_response_function](auto response, auto body_bytes) { // 1. Set worker global scope's url to response's url. worker_global_scope->set_url(response->url().value_or({})); // FIXME: 2. Initialize worker global scope's policy container given worker global scope, response, and inside settings. // FIXME: 3. If the Run CSP initialization for a global object algorithm returns "Blocked" when executed upon worker // global scope, set response to a network error. [CSP] // FIXME: 4. If worker global scope's embedder policy's value is compatible with cross-origin isolation and is shared is true, // then set agent's agent cluster's cross-origin isolation mode to "logical" or "concrete". // The one chosen is implementation-defined. // FIXME: 5. If the result of checking a global object's embedder policy with worker global scope, outside settings, // and response is false, then set response to a network error. // FIXME: 6. Set worker global scope's cross-origin isolated capability to true if agent's agent cluster's cross-origin // isolation mode is "concrete". if (!is_shared) { // 7. If is shared is false and owner's cross-origin isolated capability is false, then set worker // global scope's cross-origin isolated capability to false. // 8. If is shared is false and response's url's scheme is "data", then set worker global scope's // cross-origin isolated capability to false. } // 9. Run processCustomFetchResponse with response and bodyBytes. process_custom_fetch_response_function->function()(response, body_bytes); }; TRY(Web::Fetch::Fetching::fetch(realm, request, Web::Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); return {}; }; auto perform_fetch = Web::HTML::create_perform_the_fetch_hook(inner_settings->heap(), move(perform_fetch_function)); auto on_complete_function = [inner_settings, worker_global_scope, message_port_data = move(message_port_data), url = m_url](JS::GCPtr script) mutable { auto& realm = inner_settings->realm(); // 1. If script is null or if script's error to rethrow is non-null, then: if (!script || !script->error_to_rethrow().is_null()) { // FIXME: 1. Queue a global task on the DOM manipulation task source given worker's relevant global object to fire an event named error at worker. // FIXME: Notify Worker parent through IPC to fire an error event at Worker // FIXME 2. Run the environment discarding steps for inside settings. // 3. Abort these steps. dbgln("DedicatedWorkerHost: Unable to fetch script {} because {}", url, script ? script->error_to_rethrow().to_string_without_side_effects() : "script was null"_string); return; } // FIXME: 2. Associate worker with worker global scope. // What does this even mean? // 3. Let inside port be a new MessagePort object in inside settings's Realm. auto inside_port = Web::HTML::MessagePort::create(realm); // 4. Associate inside port with worker global scope. worker_global_scope->set_internal_port(inside_port); // 5. Entangle outside port and inside port. MUST(inside_port->transfer_receiving_steps(message_port_data)); // 6. Create a new WorkerLocation object and associate it with worker global scope. worker_global_scope->set_location(realm.heap().allocate(realm, *worker_global_scope)); // FIXME: 7. Closing orphan workers: Start monitoring the worker such that no sooner than it // stops being a protected worker, and no later than it stops being a permissible worker, // worker global scope's closing flag is set to true. // FIXME: 8. Suspending workers: Start monitoring the worker, such that whenever worker global scope's // closing flag is false and the worker is a suspendable worker, the user agent suspends // execution of script in that worker until such time as either the closing flag switches to // true or the worker stops being a suspendable worker // 9. Set inside settings's execution ready flag. inner_settings->execution_ready = true; // 10. If script is a classic script, then run the classic script script. // Otherwise, it is a module script; run the module script script. if (is(*script)) (void)static_cast(*script).run(); else (void)verify_cast(*script).run(); // FIXME: 11. Enable outside port's port message queue. // 12. If is shared is false, enable the port message queue of the worker's implicit port. if (!is_shared) { inside_port->start(); } // FIXME: 13. If is shared is true, then queue a global task on DOM manipulation task source given worker // global scope to fire an event named connect at worker global scope, using MessageEvent, // with the data attribute initialized to the empty string, the ports attribute initialized // to a new frozen array containing inside port, and the source attribute initialized to inside port. // FIXME: 14. Enable the client message queue of the ServiceWorkerContainer object whose associated service // worker client is worker global scope's relevant settings object. // 15. Event loop: Run the responsible event loop specified by inside settings until it is destroyed. inner_settings->responsible_event_loop().schedule(); // FIXME: We need to react to the closing flag being set on the responsible event loop // And use that to shutdown the WorkerHost // FIXME: 16. Clear the worker global scope's map of active timers. // FIXME: 17. Disentangle all the ports in the list of the worker's ports. // FIXME: 18. Empty worker global scope's owner set. }; auto on_complete = Web::HTML::create_on_fetch_script_complete(inner_settings->vm().heap(), move(on_complete_function)); // 14. Obtain script by switching on the value of options's type member: // classic: Fetch a classic worker script given url, outside settings, destination, inside settings, // and with onComplete and performFetch as defined below. // module: Fetch a module worker script graph given url, outside settings, destination, the value of the credentials member of options, inside settings, // and with onComplete and performFetch as defined below. if (m_type != "classic"sv) { dbgln("Unsupported script type {} for LibWeb/Worker", m_type); TODO(); } // FIXME: We don't have outside settings anymore, they live in the owner. https://github.com/whatwg/html/issues/9920 if (auto err = Web::HTML::fetch_classic_worker_script(m_url, inner_settings, destination, inner_settings, perform_fetch, on_complete); err.is_error()) { dbgln("Failed to run worker script"); // FIXME: Abort the worker properly TODO(); } } }