diff --git a/Base/home/anon/.config/SystemServer.ini b/Base/home/anon/.config/SystemServer.ini index 7f6c3e7cb2e..490bb8184bc 100644 --- a/Base/home/anon/.config/SystemServer.ini +++ b/Base/home/anon/.config/SystemServer.ini @@ -33,6 +33,15 @@ SystemModes=text,graphical MultiInstance=true AcceptSocketConnections=true +[WebWorker] +Socket=/tmp/session/%sid/portal/webworker +SocketPermissions=600 +Lazy=true +Priority=low +SystemModes=text,graphical +MultiInstance=true +AcceptSocketConnections=true + [FileSystemAccessServer] Socket=/tmp/session/%sid/portal/filesystemaccess SocketPermissions=660 diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index e5ac647fcfc..d2d52aa811a 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -215,7 +215,8 @@ add_subdirectory(SQLServer) add_subdirectory(WebContent) add_subdirectory(WebDriver) add_subdirectory(WebSocket) -add_dependencies(ladybird ImageDecoder RequestServer SQLServer WebContent WebDriver WebSocketServer headless-browser) +add_subdirectory(WebWorker) +add_dependencies(ladybird ImageDecoder RequestServer SQLServer WebContent WebDriver WebSocketServer WebWorker headless-browser) function(create_ladybird_bundle target_name) set_target_properties(${target_name} PROPERTIES @@ -240,6 +241,7 @@ function(create_ladybird_bundle target_name) COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" "${app_dir}" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" "${app_dir}" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" "${app_dir}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" "${app_dir}" COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" "${app_dir}" COMMAND "mkdir" -p "${bundle_dir}/Contents/Resources" COMMAND "iconutil" --convert icns "${SERENITY_SOURCE_DIR}/Ladybird/Icons/macos/app_icon.iconset" --output "${bundle_dir}/Contents/Resources/app_icon.icns" diff --git a/Ladybird/WebDriver/CMakeLists.txt b/Ladybird/WebDriver/CMakeLists.txt index 3efe82271bb..418ef2efd05 100644 --- a/Ladybird/WebDriver/CMakeLists.txt +++ b/Ladybird/WebDriver/CMakeLists.txt @@ -14,5 +14,5 @@ target_include_directories(WebDriver PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) target_include_directories(WebDriver PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..) target_include_directories(WebDriver PRIVATE ${SERENITY_SOURCE_DIR}/Userland) target_include_directories(WebDriver PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services) -target_link_libraries(WebDriver PRIVATE LibCore LibFileSystem LibGfx LibIPC LibJS LibMain LibWeb LibWebSocket) +target_link_libraries(WebDriver PRIVATE LibCore LibFileSystem LibGfx LibIPC LibJS LibMain LibWeb LibWebSocket LibWebView) add_dependencies(WebDriver headless-browser) diff --git a/Ladybird/WebWorker/CMakeLists.txt b/Ladybird/WebWorker/CMakeLists.txt new file mode 100644 index 00000000000..8b79581f3e5 --- /dev/null +++ b/Ladybird/WebWorker/CMakeLists.txt @@ -0,0 +1,24 @@ +set(WEBWORKER_SOURCE_DIR ${SERENITY_SOURCE_DIR}/Userland/Services/WebWorker) + +set(CMAKE_AUTOMOC OFF) +set(CMAKE_AUTORCC OFF) +set(CMAKE_AUTOUIC OFF) + +set(WEBWORKER_SOURCES + "${WEBWORKER_SOURCE_DIR}/ConnectionFromClient.cpp" + "${WEBWORKER_SOURCE_DIR}/DedicatedWorkerHost.cpp" + "${WEBWORKER_SOURCE_DIR}/PageHost.cpp" + ../HelperProcess.cpp + ../Utilities.cpp +) + +# FIXME: Add Android service + +add_library(webworker STATIC ${WEBWORKER_SOURCES}) + +target_include_directories(webworker PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services/) +target_include_directories(webworker PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..) +target_link_libraries(webworker PUBLIC LibCore LibFileSystem LibGfx LibIPC LibJS LibProtocol LibWeb LibWebView LibLocale LibImageDecoderClient LibMain) + +add_executable(WebWorker main.cpp) +target_link_libraries(WebWorker PRIVATE webworker) diff --git a/Ladybird/WebWorker/main.cpp b/Ladybird/WebWorker/main.cpp new file mode 100644 index 00000000000..ecb84f6b1a9 --- /dev/null +++ b/Ladybird/WebWorker/main.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static ErrorOr initialize_lagom_networking(); + +ErrorOr serenity_main(Main::Arguments arguments) +{ + int fd_passing_socket { -1 }; + + Core::ArgsParser args_parser; + args_parser.add_option(fd_passing_socket, "File descriptor of the fd passing socket", "fd-passing-socket", 'c', "fd-passing-socket"); + args_parser.parse(arguments); + + platform_init(); + + Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity); + Core::EventLoop event_loop; + + Web::Platform::FontPlugin::install(*new Web::Platform::FontPluginSerenity); + + TRY(initialize_lagom_networking()); + + VERIFY(fd_passing_socket >= 0); + + Web::set_resource_directory_url(TRY(String::formatted("file://{}/res", s_serenity_resource_root))); + Web::set_error_page_url(TRY(String::formatted("file://{}/res/html/error.html", s_serenity_resource_root))); + Web::set_directory_page_url(TRY(String::formatted("file://{}/res/html/directory.html", s_serenity_resource_root))); + + TRY(Web::Bindings::initialize_main_thread_vm()); + + auto client = TRY(IPC::take_over_accepted_client_from_system_server()); + client->set_fd_passing_socket(TRY(Core::LocalSocket::adopt_fd(fd_passing_socket))); + + return event_loop.exec(); +} + +static ErrorOr initialize_lagom_networking() +{ + auto candidate_request_server_paths = TRY(get_paths_for_helper_process("RequestServer"sv)); + auto request_server_client = TRY(launch_request_server_process(candidate_request_server_paths, s_serenity_resource_root)); + Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create(move(request_server_client)))); + + auto candidate_web_socket_paths = TRY(get_paths_for_helper_process("WebSocket"sv)); + auto web_socket_client = TRY(launch_web_socket_process(candidate_web_socket_paths, s_serenity_resource_root)); + Web::WebSockets::WebSocketClientManager::initialize(TRY(WebView::WebSocketClientManagerAdapter::try_create(move(web_socket_client)))); + + return {}; +} diff --git a/Ladybird/cmake/InstallRules.cmake b/Ladybird/cmake/InstallRules.cmake index cac5c420f05..6978382482d 100644 --- a/Ladybird/cmake/InstallRules.cmake +++ b/Ladybird/cmake/InstallRules.cmake @@ -4,7 +4,7 @@ include(GNUInstallDirs) set(package ladybird) -set(ladybird_applications ladybird SQLServer WebContent WebDriver WebSocketServer RequestServer ImageDecoder headless-browser) +set(ladybird_applications ladybird SQLServer WebContent WebDriver WebSocketServer RequestServer ImageDecoder WebWorker headless-browser) set(app_install_targets ${ladybird_applications}) @@ -45,7 +45,7 @@ macro(install_service_lib service) endif() endif() endmacro() -foreach(service IN LISTS webcontent requestserver websocket) +foreach(service IN LISTS webcontent requestserver websocket webworker) install_service_lib(${service}) endforeach() diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 98308de60ec..df16526a96d 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -382,6 +382,7 @@ set(SOURCES HTML/Scripting/Script.cpp HTML/Scripting/TemporaryExecutionContext.cpp HTML/Scripting/WindowEnvironmentSettingsObject.cpp + HTML/Scripting/WorkerEnvironmentSettingsObject.cpp HTML/SessionHistoryEntry.cpp HTML/SharedImageRequest.cpp HTML/SourceSet.cpp @@ -403,6 +404,7 @@ set(SOURCES HTML/WindowOrWorkerGlobalScope.cpp HTML/WindowProxy.cpp HTML/Worker.cpp + HTML/WorkerAgent.cpp HTML/WorkerDebugConsoleClient.cpp HTML/WorkerGlobalScope.cpp HTML/WorkerLocation.cpp @@ -632,8 +634,12 @@ set(SOURCES XHR/XMLHttpRequestUpload.cpp XML/XMLDocumentBuilder.cpp XLink/AttributeNames.cpp + Worker/WebWorkerClient.cpp ) +compile_ipc(Worker/WebWorkerClient.ipc Worker/WebWorkerClientEndpoint.h) +compile_ipc(Worker/WebWorkerServer.ipc Worker/WebWorkerServerEndpoint.h) + invoke_generator( "AriaRoles.cpp" Lagom::GenerateAriaRoles @@ -659,6 +665,8 @@ set(GENERATED_SOURCES CSS/ValueID.cpp MathML/MathMLStyleSheetSource.cpp SVG/SVGStyleSheetSource.cpp + Worker/WebWorkerClientEndpoint.h + Worker/WebWorkerServerEndpoint.h ) serenity_lib(LibWeb web) diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.cpp new file mode 100644 index 00000000000..b73b3b2550d --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::HTML { + +JS::NonnullGCPtr WorkerEnvironmentSettingsObject::setup(NonnullOwnPtr execution_context /* FIXME: null or an environment reservedEnvironment, a URL topLevelCreationURL, and an origin topLevelOrigin */) +{ + auto realm = execution_context->realm; + VERIFY(realm); + + auto& worker = verify_cast(realm->global_object()); + + auto settings_object = realm->heap().allocate(*realm, move(execution_context), worker); + settings_object->target_browsing_context = nullptr; + + auto intrinsics = realm->heap().allocate(*realm, *realm); + auto host_defined = make(settings_object, intrinsics); + realm->set_host_defined(move(host_defined)); + + // Non-Standard: We cannot fully initialize worker object until *after* the we set up + // the realm's [[HostDefined]] internal slot as the internal slot contains the web platform intrinsics + worker.initialize_web_interfaces({}); + + return settings_object; +} + +void WorkerEnvironmentSettingsObject::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_global_scope); +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.h b/Userland/Libraries/LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.h index 40b3191af82..245009ce401 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.h +++ b/Userland/Libraries/LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.h @@ -7,7 +7,6 @@ #pragma once #include -#include #include #include @@ -18,27 +17,13 @@ class WorkerEnvironmentSettingsObject final JS_CELL(WindowEnvironmentSettingsObject, EnvironmentSettingsObject); public: - WorkerEnvironmentSettingsObject(NonnullOwnPtr execution_context) + WorkerEnvironmentSettingsObject(NonnullOwnPtr execution_context, JS::NonnullGCPtr global_scope) : EnvironmentSettingsObject(move(execution_context)) + , m_global_scope(global_scope) { } - static JS::NonnullGCPtr setup(NonnullOwnPtr execution_context /* FIXME: null or an environment reservedEnvironment, a URL topLevelCreationURL, and an origin topLevelOrigin */) - { - auto realm = execution_context->realm; - VERIFY(realm); - auto settings_object = realm->heap().allocate(*realm, move(execution_context)); - settings_object->target_browsing_context = nullptr; - - auto intrinsics = realm->heap().allocate(*realm, *realm); - auto host_defined = make(settings_object, intrinsics); - realm->set_host_defined(move(host_defined)); - - // FIXME: Shared workers should use the shared worker method - Bindings::add_dedicated_worker_exposed_interfaces(realm->global_object()); - - return settings_object; - } + static JS::NonnullGCPtr setup(NonnullOwnPtr execution_context /* FIXME: null or an environment reservedEnvironment, a URL topLevelCreationURL, and an origin topLevelOrigin */); virtual ~WorkerEnvironmentSettingsObject() override = default; @@ -47,13 +32,17 @@ public: AK::URL api_base_url() override { return m_url; } Origin origin() override { return m_origin; } PolicyContainer policy_container() override { return m_policy_container; } - CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() override { TODO(); } + CanUseCrossOriginIsolatedAPIs cross_origin_isolated_capability() override { return CanUseCrossOriginIsolatedAPIs::No; } private: + virtual void visit_edges(JS::Cell::Visitor&) override; + DeprecatedString m_api_url_character_encoding; AK::URL m_url; HTML::Origin m_origin; HTML::PolicyContainer m_policy_container; + + JS::NonnullGCPtr m_global_scope; }; } diff --git a/Userland/Libraries/LibWeb/HTML/Worker.cpp b/Userland/Libraries/LibWeb/HTML/Worker.cpp index 99292f8c668..31b922b4eae 100644 --- a/Userland/Libraries/LibWeb/HTML/Worker.cpp +++ b/Userland/Libraries/LibWeb/HTML/Worker.cpp @@ -21,9 +21,6 @@ Worker::Worker(String const& script_url, WorkerOptions const options, DOM::Docum , m_script_url(script_url) , m_options(options) , m_document(&document) - , m_custom_data() - , m_worker_vm(JS::VM::create(adopt_own(m_custom_data)).release_value_but_fixme_should_propagate_errors()) - , m_implicit_port(MessagePort::create(document.realm())) { } @@ -37,22 +34,12 @@ void Worker::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_document); - visitor.visit(m_inner_settings); - visitor.visit(m_implicit_port); visitor.visit(m_outside_port); - - // These are in a separate VM and shouldn't be visited - visitor.ignore(m_worker_realm); - visitor.ignore(m_worker_scope); } // https://html.spec.whatwg.org/multipage/workers.html#dom-worker WebIDL::ExceptionOr> Worker::create(String const& script_url, WorkerOptions const options, DOM::Document& document) { - // NOTE: We don't start a worker because they're not properly implemented yet and would likely crash. - dbgln("FIXME: Implement web workers"); - return WebIDL::NotSupportedError::create(document.realm(), "Web workers not supported yet"_fly_string); - dbgln_if(WEB_WORKER_DEBUG, "WebWorker: Creating worker with script_url = {}", script_url); // Returns a new Worker object. scriptURL will be fetched and executed in the background, @@ -98,12 +85,10 @@ WebIDL::ExceptionOr> Worker::create(String const& scrip } // https://html.spec.whatwg.org/multipage/workers.html#run-a-worker -void Worker::run_a_worker(AK::URL& url, EnvironmentSettingsObject& outside_settings, MessagePort& outside_port, WorkerOptions const& options) +void Worker::run_a_worker(AK::URL& url, EnvironmentSettingsObject& outside_settings, MessagePort&, WorkerOptions const& options) { // 1. Let is shared be true if worker is a SharedWorker object, and false otherwise. // FIXME: SharedWorker support - auto is_shared = false; - auto is_top_level = false; // 2. Let owner be the relevant owner to add given outside settings. // FIXME: Support WorkerGlobalScope options @@ -119,204 +104,9 @@ void Worker::run_a_worker(AK::URL& url, EnvironmentSettingsObject& outside_setti // 6. Let agent be the result of obtaining a dedicated/shared worker agent given outside settings // and is shared. Run the rest of these steps in that agent. - // NOTE: This is effectively the worker's vm - // 7. Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations: - auto realm_execution_context = Bindings::create_a_new_javascript_realm( - *m_worker_vm, - [&](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(); - // FIXME: Make and use subclasses of WorkerGlobalScope, however this requires JS::GlobalObject to - // play nicely with the IDL interpreter, to make spec-compliant extensions, which it currently does not. - m_worker_scope = m_worker_vm->heap().allocate_without_realm(realm); - return m_worker_scope.ptr(); - }, - nullptr); - - auto& console_object = *realm_execution_context->realm->intrinsics().console_object(); - m_worker_realm = realm_execution_context->realm; - - m_console = adopt_ref(*new WorkerDebugConsoleClient(console_object.console())); - console_object.console().set_client(*m_console); - - // FIXME: This should be done with IDL - u8 attr = JS::Attribute::Writable | JS::Attribute::Enumerable | JS::Attribute::Configurable; - m_worker_scope->define_native_function( - m_worker_scope->shape().realm(), - "postMessage", - [this](auto& vm) { - // This is the implementation of the function that the spawned worked calls - - // https://html.spec.whatwg.org/multipage/workers.html#dom-dedicatedworkerglobalscope-postmessage - // The postMessage(message, transfer) and postMessage(message, options) methods on DedicatedWorkerGlobalScope - // objects act as if, when invoked, it immediately invoked the respective postMessage(message, transfer) and - // postMessage(message, options) on the port, with the same arguments, and returned the same return value - - auto message = vm.argument(0); - // FIXME: `transfer` not support by post_message yet - - dbgln_if(WEB_WORKER_DEBUG, "WebWorker: Inner post_message"); - - // FIXME: This is a bit of a hack, in reality, we should m_outside_port->post_message and the onmessage event - // should bubble up to the Worker itself from there. - - auto& event_loop = get_vm_event_loop(m_document->realm().vm()); - - event_loop.task_queue().add(HTML::Task::create(HTML::Task::Source::PostedMessage, nullptr, [this, message] { - MessageEventInit event_init {}; - event_init.data = message; - event_init.origin = ""_string; - dispatch_event(MessageEvent::create(*m_worker_realm, HTML::EventNames::message, event_init)); - })); - - return JS::js_undefined(); - }, - 2, attr); - - // 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. - - // 9. Set up a worker environment settings object with realm execution context, - // outside settings, and unsafeWorkerCreationTime, and let inside settings be the result. - m_inner_settings = WorkerEnvironmentSettingsObject::setup(move(realm_execution_context)); - - // 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. - - // 14. Obtain script by switching on the value of options's type member: - // classic: Fetch a classic worker script given url, outside settings, destination, and inside settings. - // module: Fetch a module worker script graph given url, outside settings, destination, the value of the - // credentials member of options, and inside settings. - if (options.type != "classic") { - dbgln_if(WEB_WORKER_DEBUG, "Unsupported script type {} for LibWeb/Worker", options.type); - TODO(); - } - - ResourceLoader::the().load( - url, - [this, is_shared, is_top_level, url, &outside_port](auto data, auto&, auto) { - // In both cases, to perform the fetch given request, perform the following steps if the is top-level flag is set: - if (is_top_level) { - // 1. Set request's reserved client to inside settings. - - // 2. Fetch request, and asynchronously wait to run the remaining steps - // as part of fetch's process response for the response response. - // Implied - - // 3. Set worker global scope's url to response's url. - - // 4. Initialize worker global scope's policy container given worker global scope, response, and inside settings. - // FIXME: implement policy container - - // 5. 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: CSP support - - // 6. 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: CORS policy support - - // 7. 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: Embed policy support - - // 8. Set worker global scope's cross-origin isolated capability to true if agent's agent cluster's cross-origin - // isolation mode is "concrete". - // FIXME: CORS policy support - - if (!is_shared) { - // 9. 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. - // 10. 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. - } - } - - if (data.is_null()) { - dbgln_if(WEB_WORKER_DEBUG, "WebWorker: Failed to load {}", url); - return; - } - - // Asynchronously complete the perform the fetch steps with response. - dbgln_if(WEB_WORKER_DEBUG, "WebWorker: Script ready!"); - auto script = ClassicScript::create(url.to_deprecated_string(), data, *m_inner_settings, AK::URL()); - - // NOTE: Steps 15-31 below pick up after step 14 in the main context, steps 1-10 above - // are only for validation when used in a top-level case (ie: from a Window) - - // 15. Associate worker with worker global scope. - // FIXME: Global scope association - - // 16. Let inside port be a new MessagePort object in inside settings's Realm. - auto inside_port = MessagePort::create(m_inner_settings->realm()); - - // 17. Associate inside port with worker global scope. - // FIXME: Global scope association - - // 18. Entangle outside port and inside port. - outside_port.entangle_with(*inside_port); - - // 19. Create a new WorkerLocation object and associate it with worker global scope. - - // 20. 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: Worker monitoring and cleanup - - // 21. 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 - // FIXME: Worker suspending - - // 22. Set inside settings's execution ready flag. - // FIXME: Implement worker settings object - - // 23. If script is a classic script, then run the classic script script. - // Otherwise, it is a module script; run the module script script. - auto result = script->run(); - - // 24. Enable outside port's port message queue. - outside_port.start(); - - // 25. If is shared is false, enable the port message queue of the worker's implicit port. - if (!is_shared) - m_implicit_port->start(); - - // 26. 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: Shared worker support - - // 27. Enable the client message queue of the ServiceWorkerContainer object whose associated service - // worker client is worker global scope's relevant settings object. - // FIXME: Understand....and support worker global settings - - // 28. Event loop: Run the responsible event loop specified by inside settings until it is destroyed. - }, - [](auto&, auto, auto, auto&) { - dbgln_if(WEB_WORKER_DEBUG, "WebWorker: HONK! Failed to load script."); - }); + // Note: This spawns a new process to act as the 'agent' for the worker. + m_agent = heap().allocate_without_realm(url, options); } // https://html.spec.whatwg.org/multipage/workers.html#dom-worker-terminate diff --git a/Userland/Libraries/LibWeb/HTML/Worker.h b/Userland/Libraries/LibWeb/HTML/Worker.h index 4b9d1986068..e9638d9209c 100644 --- a/Userland/Libraries/LibWeb/HTML/Worker.h +++ b/Userland/Libraries/LibWeb/HTML/Worker.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -25,12 +26,6 @@ namespace Web::HTML { -struct WorkerOptions { - String type { "classic"_string }; - String credentials { "same-origin"_string }; - String name { String {} }; -}; - // https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-worker-interface class Worker : public DOM::EventTarget { WEB_PLATFORM_OBJECT(Worker, DOM::EventTarget); @@ -49,7 +44,6 @@ public: virtual ~Worker() = default; - MessagePort* implicit_message_port() { return m_implicit_port.ptr(); } JS::GCPtr outside_message_port() { return m_outside_port; } #undef __ENUMERATE @@ -63,11 +57,6 @@ protected: Worker(String const&, const WorkerOptions, DOM::Document&); private: - static HTML::EventLoop& get_vm_event_loop(JS::VM& target_vm) - { - return static_cast(target_vm.custom_data())->event_loop; - } - virtual void initialize(JS::Realm&) override; virtual void visit_edges(Cell::Visitor&) override; @@ -75,19 +64,9 @@ private: WorkerOptions m_options; JS::GCPtr m_document; - - Bindings::WebEngineCustomData m_custom_data; - - NonnullRefPtr m_worker_vm; - JS::GCPtr m_inner_settings; - RefPtr m_console; - - JS::NonnullGCPtr m_implicit_port; JS::GCPtr m_outside_port; - // NOTE: These are inside the worker VM. - JS::GCPtr m_worker_realm; - JS::GCPtr m_worker_scope; + JS::GCPtr m_agent; void run_a_worker(AK::URL& url, EnvironmentSettingsObject& outside_settings, MessagePort& outside_port, WorkerOptions const& options); }; diff --git a/Userland/Libraries/LibWeb/HTML/WorkerAgent.cpp b/Userland/Libraries/LibWeb/HTML/WorkerAgent.cpp new file mode 100644 index 00000000000..20da5d4f806 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/WorkerAgent.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +// FIXME: Deduplicate this code with ladybird!! + +#ifndef AK_OS_SERENITY +namespace { + +ErrorOr application_directory() +{ + auto current_executable_path = TRY(Core::System::current_executable_path()); + auto dirname = LexicalPath::dirname(current_executable_path.to_deprecated_string()); + return String::from_deprecated_string(dirname); +} + +ErrorOr> get_paths_for_helper_process(StringView process_name) +{ + auto application_path = TRY(application_directory()); + Vector paths; + + TRY(paths.try_append(TRY(String::formatted("{}/{}", application_path, process_name)))); + TRY(paths.try_append(TRY(String::formatted("./{}", process_name)))); + // NOTE: Add platform-specific paths here + return paths; +} + +ErrorOr> launch_web_worker_process(ReadonlySpan candidate_web_content_paths) +{ + int socket_fds[2] {}; + TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds)); + + int ui_fd = socket_fds[0]; + int wc_fd = socket_fds[1]; + + int fd_passing_socket_fds[2] {}; + TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, fd_passing_socket_fds)); + + int ui_fd_passing_fd = fd_passing_socket_fds[0]; + int wc_fd_passing_fd = fd_passing_socket_fds[1]; + + if (auto child_pid = TRY(Core::System::fork()); child_pid == 0) { + TRY(Core::System::close(ui_fd_passing_fd)); + TRY(Core::System::close(ui_fd)); + + auto takeover_string = TRY(String::formatted("WebWorker:{}", wc_fd)); + TRY(Core::System::setenv("SOCKET_TAKEOVER"sv, takeover_string, true)); + + auto webcontent_fd_passing_socket_string = TRY(String::number(wc_fd_passing_fd)); + + ErrorOr result; + for (auto const& path : candidate_web_content_paths) { + if (Core::System::access(path, X_OK).is_error()) + continue; + + auto arguments = Vector { + path.bytes_as_string_view(), + "--fd-passing-socket"sv, + webcontent_fd_passing_socket_string + }; + + result = Core::System::exec(arguments[0], arguments.span(), Core::System::SearchInPath::Yes); + if (!result.is_error()) + break; + } + + if (result.is_error()) + warnln("Could not launch any of {}: {}", candidate_web_content_paths, result.error()); + VERIFY_NOT_REACHED(); + } + + TRY(Core::System::close(wc_fd_passing_fd)); + TRY(Core::System::close(wc_fd)); + + auto socket = TRY(Core::LocalSocket::adopt_fd(ui_fd)); + TRY(socket->set_blocking(true)); + + auto new_client = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) Web::HTML::WebWorkerClient(move(socket)))); + new_client->set_fd_passing_socket(TRY(Core::LocalSocket::adopt_fd(ui_fd_passing_fd))); + + return new_client; +} +} +#endif + +namespace Web::HTML { + +WorkerAgent::WorkerAgent(AK::URL url, WorkerOptions const& options) + : m_worker_options(options) + , m_url(move(url)) +{ +#ifndef AK_OS_SERENITY + // FIXME: Add factory function + auto paths = MUST(get_paths_for_helper_process("WebWorker"sv)); + m_worker_ipc = MUST(launch_web_worker_process(paths)); +#else + m_worker_ipc = MUST(Web::HTML::WebWorkerClient::try_create()); +#endif + + int fds[2] = {}; + MUST(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, fds)); + m_message_port_fd = fds[0]; + + m_worker_ipc->async_start_dedicated_worker(m_url, options.type, options.credentials, options.name, fds[1]); +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/WorkerAgent.h b/Userland/Libraries/LibWeb/HTML/WorkerAgent.h new file mode 100644 index 00000000000..57a1baa56f5 --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/WorkerAgent.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +struct WorkerOptions { + String type { "classic"_string }; + String credentials { "same-origin"_string }; + String name { String {} }; +}; + +struct WorkerAgent : JS::Cell { + JS_CELL(Agent, JS::Cell); + + WorkerAgent(AK::URL url, WorkerOptions const& options); + + RefPtr m_worker_ipc; + +private: + WorkerOptions m_worker_options; + AK::URL m_url; + + // TODO: associate with MessagePorts? + int m_message_port_fd; +}; + +} diff --git a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp index 3811f34e71a..100d108bb6a 100644 --- a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp +++ b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.cpp @@ -5,7 +5,9 @@ */ #include +#include #include +#include #include #include #include @@ -16,17 +18,26 @@ namespace Web::HTML { -WorkerGlobalScope::WorkerGlobalScope(JS::Realm& realm) +WorkerGlobalScope::WorkerGlobalScope(JS::Realm& realm, Web::Page& page) : DOM::EventTarget(realm) - + , m_page(page) { } WorkerGlobalScope::~WorkerGlobalScope() = default; -void WorkerGlobalScope::initialize(JS::Realm& realm) +void WorkerGlobalScope::initialize_web_interfaces(Badge) { + auto& realm = this->realm(); Base::initialize(realm); + + // FIXME: Handle shared worker + add_dedicated_worker_exposed_interfaces(*this); + + Object::set_prototype(&Bindings::ensure_web_prototype(realm, "WorkerGlobalScope")); + + WindowOrWorkerGlobalScopeMixin::initialize(realm); + m_navigator = WorkerNavigator::create(*this); } diff --git a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h index 44feb2fdd79..b665e44ffde 100644 --- a/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h +++ b/Userland/Libraries/LibWeb/HTML/WorkerGlobalScope.h @@ -41,6 +41,16 @@ public: virtual Bindings::PlatformObject& this_impl() override { return *this; } virtual Bindings::PlatformObject const& this_impl() const override { return *this; } + using WindowOrWorkerGlobalScopeMixin::atob; + using WindowOrWorkerGlobalScopeMixin::btoa; + using WindowOrWorkerGlobalScopeMixin::clear_interval; + using WindowOrWorkerGlobalScopeMixin::clear_timeout; + using WindowOrWorkerGlobalScopeMixin::fetch; + using WindowOrWorkerGlobalScopeMixin::queue_microtask; + using WindowOrWorkerGlobalScopeMixin::set_interval; + using WindowOrWorkerGlobalScopeMixin::set_timeout; + using WindowOrWorkerGlobalScopeMixin::structured_clone; + // Following methods are from the WorkerGlobalScope IDL definition // https://html.spec.whatwg.org/multipage/workers.html#the-workerglobalscope-common-interface @@ -67,16 +77,17 @@ public: // this is not problematic as it cannot be observed from script. void set_location(JS::NonnullGCPtr loc) { m_location = move(loc); } + void initialize_web_interfaces(Badge); + + Web::Page* page() { return &m_page; } + protected: - explicit WorkerGlobalScope(JS::Realm&); + explicit WorkerGlobalScope(JS::Realm&, Web::Page&); private: - virtual void initialize(JS::Realm&) override; - virtual void visit_edges(Cell::Visitor&) override; JS::GCPtr m_location; - JS::GCPtr m_navigator; // FIXME: Add all these internal slots @@ -110,6 +121,8 @@ private: // https://html.spec.whatwg.org/multipage/workers.html#concept-workerglobalscope-cross-origin-isolated-capability bool m_cross_origin_isolated_capability { false }; + + Web::Page& m_page; }; } diff --git a/Userland/Libraries/LibWeb/Worker/WebWorkerClient.cpp b/Userland/Libraries/LibWeb/Worker/WebWorkerClient.cpp new file mode 100644 index 00000000000..0f98d4d7aeb --- /dev/null +++ b/Userland/Libraries/LibWeb/Worker/WebWorkerClient.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace Web::HTML { + +void WebWorkerClient::die() +{ + // FIXME: Notify WorkerAgent that the worker is ded +} + +WebWorkerClient::WebWorkerClient(NonnullOwnPtr socket) + : IPC::ConnectionToServer(*this, move(socket)) +{ +} + +} diff --git a/Userland/Libraries/LibWeb/Worker/WebWorkerClient.h b/Userland/Libraries/LibWeb/Worker/WebWorkerClient.h new file mode 100644 index 00000000000..1e5bb412572 --- /dev/null +++ b/Userland/Libraries/LibWeb/Worker/WebWorkerClient.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::HTML { + +class WebWorkerClient final + : public IPC::ConnectionToServer + , public WebWorkerClientEndpoint { + IPC_CLIENT_CONNECTION(WebWorkerClient, "/tmp/session/%sid/portal/webworker"sv); + +public: + explicit WebWorkerClient(NonnullOwnPtr); + +private: + virtual void die() override; +}; + +} diff --git a/Userland/Libraries/LibWeb/Worker/WebWorkerClient.ipc b/Userland/Libraries/LibWeb/Worker/WebWorkerClient.ipc new file mode 100644 index 00000000000..6af31841eff --- /dev/null +++ b/Userland/Libraries/LibWeb/Worker/WebWorkerClient.ipc @@ -0,0 +1,2 @@ +endpoint WebWorkerClient { +} diff --git a/Userland/Libraries/LibWeb/Worker/WebWorkerServer.ipc b/Userland/Libraries/LibWeb/Worker/WebWorkerServer.ipc new file mode 100644 index 00000000000..53fb5483fca --- /dev/null +++ b/Userland/Libraries/LibWeb/Worker/WebWorkerServer.ipc @@ -0,0 +1,9 @@ +#include +#include + +endpoint WebWorkerServer { + + start_dedicated_worker(URL url, String type, String credentials, String name, IPC::File message_port) =| + + handle_file_return(i32 error, Optional file, i32 request_id) =| +} diff --git a/Userland/Services/CMakeLists.txt b/Userland/Services/CMakeLists.txt index 3950bc357c7..82e72923de9 100644 --- a/Userland/Services/CMakeLists.txt +++ b/Userland/Services/CMakeLists.txt @@ -27,5 +27,6 @@ if (SERENITYOS) add_subdirectory(WebContent) add_subdirectory(WebDriver) add_subdirectory(WebSocket) + add_subdirectory(WebWorker) add_subdirectory(WindowServer) endif() diff --git a/Userland/Services/WebContent/main.cpp b/Userland/Services/WebContent/main.cpp index 4de66d6d678..df2a2fffd47 100644 --- a/Userland/Services/WebContent/main.cpp +++ b/Userland/Services/WebContent/main.cpp @@ -41,6 +41,7 @@ ErrorOr serenity_main(Main::Arguments) TRY(Core::System::unveil("/tmp/session/%sid/portal/request", "rw")); TRY(Core::System::unveil("/tmp/session/%sid/portal/image", "rw")); TRY(Core::System::unveil("/tmp/session/%sid/portal/websocket", "rw")); + TRY(Core::System::unveil("/tmp/session/%sid/portal/webworker", "rw")); TRY(Core::System::unveil(nullptr, nullptr)); Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity); diff --git a/Userland/Services/WebDriver/CMakeLists.txt b/Userland/Services/WebDriver/CMakeLists.txt index 4bf0a016d6e..e4650956f3e 100644 --- a/Userland/Services/WebDriver/CMakeLists.txt +++ b/Userland/Services/WebDriver/CMakeLists.txt @@ -17,4 +17,4 @@ set(GENERATED_SOURCES ) serenity_bin(WebDriver) -target_link_libraries(WebDriver PRIVATE LibCore LibHTTP LibMain LibIPC LibWeb LibGfx) +target_link_libraries(WebDriver PRIVATE LibCore LibHTTP LibMain LibIPC LibWeb LibGfx LibWebView) diff --git a/Userland/Services/WebWorker/CMakeLists.txt b/Userland/Services/WebWorker/CMakeLists.txt new file mode 100644 index 00000000000..2e706f5eca8 --- /dev/null +++ b/Userland/Services/WebWorker/CMakeLists.txt @@ -0,0 +1,21 @@ +serenity_component( + WebWorker + TARGETS WebWorker + DEPENDS WebContent +) + +set(SOURCES + ConnectionFromClient.cpp + DedicatedWorkerHost.cpp + PageHost.cpp + main.cpp +) + +set(GENERATED_SOURCES + ../Libraries/LibWeb/Worker/WebWorkerClientEndpoint.h + ../Libraries/LibWeb/Worker/WebWorkerServerEndpoint.h +) + +serenity_bin(WebWorker) +target_link_libraries(WebWorker PRIVATE LibCore LibFileSystem LibGfx LibIPC LibJS LibWeb LibWebView LibLocale LibMain) +link_with_locale_data(WebWorker) diff --git a/Userland/Services/WebWorker/ConnectionFromClient.cpp b/Userland/Services/WebWorker/ConnectionFromClient.cpp new file mode 100644 index 00000000000..72fb2fdc909 --- /dev/null +++ b/Userland/Services/WebWorker/ConnectionFromClient.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace WebWorker { + +void ConnectionFromClient::die() +{ + // FIXME: Do something here (shutdown process/script gracefully?) +} + +void ConnectionFromClient::request_file(Web::FileRequest request) +{ + // FIXME: Route this to FSAS or Brower chrome as appropriate instead of allowing + // the WebWorker process filesystem access + auto path = request.path(); + auto request_id = ++last_id; + + m_requested_files.set(request_id, move(request)); + + auto file = Core::File::open(path, Core::File::OpenMode::Read); + + if (file.is_error()) + handle_file_return(file.error().code(), {}, request_id); + else + handle_file_return(0, IPC::File(*file.value()), request_id); +} + +ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr socket) + : IPC::ConnectionFromClient(*this, move(socket), 1) + , m_page_host(PageHost::create(*this)) +{ +} + +ConnectionFromClient::~ConnectionFromClient() = default; + +Web::Page& ConnectionFromClient::page() +{ + return m_page_host->page(); +} + +Web::Page const& ConnectionFromClient::page() const +{ + return m_page_host->page(); +} + +void ConnectionFromClient::start_dedicated_worker(AK::URL const& url, String const& type, String const&, String const&, IPC::File const&) +{ + m_worker_host = make_ref_counted(page(), url, type); + + m_worker_host->run(); +} + +void ConnectionFromClient::handle_file_return(i32 error, Optional const& file, i32 request_id) +{ + auto file_request = m_requested_files.take(request_id); + + VERIFY(file_request.has_value()); + VERIFY(file_request.value().on_file_request_finish); + + file_request.value().on_file_request_finish(error != 0 ? Error::from_errno(error) : ErrorOr { file->take_fd() }); +} + +} diff --git a/Userland/Services/WebWorker/ConnectionFromClient.h b/Userland/Services/WebWorker/ConnectionFromClient.h new file mode 100644 index 00000000000..91bd0b22e25 --- /dev/null +++ b/Userland/Services/WebWorker/ConnectionFromClient.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace WebWorker { + +class ConnectionFromClient final + : public IPC::ConnectionFromClient { + C_OBJECT(ConnectionFromClient); + +public: + virtual ~ConnectionFromClient() override; + + virtual void die() override; + + void request_file(Web::FileRequest); + + PageHost& page_host() { return *m_page_host; } + PageHost const& page_host() const { return *m_page_host; } + +private: + explicit ConnectionFromClient(NonnullOwnPtr); + + Web::Page& page(); + Web::Page const& page() const; + + virtual void start_dedicated_worker(AK::URL const& url, String const&, String const&, String const&, IPC::File const&) override; + virtual void handle_file_return(i32 error, Optional const& file, i32 request_id) override; + + NonnullOwnPtr m_page_host; + + // FIXME: Route console messages to the Browser UI using a ConsoleClient + + HashMap m_requested_files {}; + int last_id { 0 }; + + RefPtr m_worker_host; +}; + +} diff --git a/Userland/Services/WebWorker/DedicatedWorkerHost.cpp b/Userland/Services/WebWorker/DedicatedWorkerHost.cpp new file mode 100644 index 00000000000..3fe9e810c1d --- /dev/null +++ b/Userland/Services/WebWorker/DedicatedWorkerHost.cpp @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace WebWorker { + +DedicatedWorkerHost::DedicatedWorkerHost(Web::Page& page, AK::URL url, String type) + : m_worker_vm(JS::VM::create(make()).release_value_but_fixme_should_propagate_errors()) + , m_page(page) + , m_url(move(url)) + , m_type(move(type)) +{ + // FIXME: We need to attach all the HostDefined hooks from MainThreadVM onto this VM in order to load + // module scripts in Workers. +} + +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() +{ + 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( + *m_worker_vm, + [this](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 m_worker_vm->heap().allocate_without_realm(realm, m_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(move(realm_execution_context)); + inner_settings->responsible_event_loop().set_vm(*m_worker_vm); + + 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](JS::GCPtr script) { + 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("Didn't get my script :("); + return; + } + + // FIXME: 2. Associate worker with worker global scope. + // What does this even mean? + + // FIXME: 3. Let inside port be a new MessagePort object in inside settings's Realm. + // FIXME: 4. Associate inside port with worker global scope. + // FIXME: 5. Entangle outside port and inside port. + + // 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. + + // FIXME: 12. If is shared is false, enable the port message queue of the worker's implicit port. + + // 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(); + } +} + +} diff --git a/Userland/Services/WebWorker/DedicatedWorkerHost.h b/Userland/Services/WebWorker/DedicatedWorkerHost.h new file mode 100644 index 00000000000..31623e15cd3 --- /dev/null +++ b/Userland/Services/WebWorker/DedicatedWorkerHost.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace WebWorker { + +class DedicatedWorkerHost : public RefCounted { +public: + explicit DedicatedWorkerHost(Web::Page&, AK::URL url, String type); + ~DedicatedWorkerHost(); + + void run(); + +private: + NonnullRefPtr m_worker_vm; + RefPtr m_console; + Web::Page& m_page; + + AK::URL m_url; + String m_type; +}; + +} diff --git a/Userland/Services/WebWorker/Forward.h b/Userland/Services/WebWorker/Forward.h new file mode 100644 index 00000000000..e3480520807 --- /dev/null +++ b/Userland/Services/WebWorker/Forward.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace WebWorker { + +class ConnectionFromClient; +class DedicatedWorkerHost; +class PageHost; + +} diff --git a/Userland/Services/WebWorker/PageHost.cpp b/Userland/Services/WebWorker/PageHost.cpp new file mode 100644 index 00000000000..90da83dae84 --- /dev/null +++ b/Userland/Services/WebWorker/PageHost.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace WebWorker { + +PageHost::~PageHost() = default; + +Web::Page& PageHost::page() +{ + return *m_page; +} + +Web::Page const& PageHost::page() const +{ + return *m_page; +} + +Gfx::Palette PageHost::palette() const +{ + return Gfx::Palette(*m_palette_impl); +} + +void PageHost::setup_palette() +{ + // FIXME: We don't actually need a palette :thonk: + auto buffer_or_error = Core::AnonymousBuffer::create_with_size(sizeof(Gfx::SystemTheme)); + VERIFY(!buffer_or_error.is_error()); + auto buffer = buffer_or_error.release_value(); + auto* theme = buffer.data(); + theme->color[to_underlying(Gfx::ColorRole::Window)] = Color::Magenta; + theme->color[to_underlying(Gfx::ColorRole::WindowText)] = Color::Cyan; + m_palette_impl = Gfx::PaletteImpl::create_with_anonymous_buffer(buffer); +} + +bool PageHost::is_connection_open() const +{ + return m_client.is_open(); +} + +Web::DevicePixelRect PageHost::screen_rect() const +{ + return {}; +} + +double PageHost::device_pixels_per_css_pixel() const +{ + return 1.0; +} + +Web::CSS::PreferredColorScheme PageHost::preferred_color_scheme() const +{ + return Web::CSS::PreferredColorScheme::Auto; +} + +void PageHost::paint(Web::DevicePixelRect const&, Gfx::Bitmap&) +{ +} + +void PageHost::request_file(Web::FileRequest request) +{ + m_client.request_file(move(request)); +} + +PageHost::PageHost(ConnectionFromClient& client) + : m_client(client) + , m_page(make(*this)) +{ + setup_palette(); +} + +} diff --git a/Userland/Services/WebWorker/PageHost.h b/Userland/Services/WebWorker/PageHost.h new file mode 100644 index 00000000000..02ed7ebe29d --- /dev/null +++ b/Userland/Services/WebWorker/PageHost.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace WebWorker { + +class PageHost final : public Web::PageClient { + AK_MAKE_NONCOPYABLE(PageHost); + AK_MAKE_NONMOVABLE(PageHost); + +public: + static NonnullOwnPtr create(ConnectionFromClient& client) { return adopt_own(*new PageHost(client)); } + virtual ~PageHost(); + + virtual Web::Page& page() override; + virtual Web::Page const& page() const override; + virtual bool is_connection_open() const override; + virtual Gfx::Palette palette() const override; + virtual Web::DevicePixelRect screen_rect() const override; + virtual double device_pixels_per_css_pixel() const override; + virtual Web::CSS::PreferredColorScheme preferred_color_scheme() const override; + virtual void paint(Web::DevicePixelRect const&, Gfx::Bitmap&) override; + virtual void request_file(Web::FileRequest) override; + +private: + explicit PageHost(ConnectionFromClient&); + + void setup_palette(); + + ConnectionFromClient& m_client; + NonnullOwnPtr m_page; + RefPtr m_palette_impl; +}; + +} diff --git a/Userland/Services/WebWorker/main.cpp b/Userland/Services/WebWorker/main.cpp new file mode 100644 index 00000000000..655fd3929ac --- /dev/null +++ b/Userland/Services/WebWorker/main.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ErrorOr serenity_main(Main::Arguments) +{ + TRY(Core::System::pledge("stdio recvfd sendfd accept unix rpath thread proc")); + + Core::EventLoop event_loop; + TRY(Core::System::unveil("/res", "r")); + TRY(Core::System::unveil("/etc/timezone", "r")); + TRY(Core::System::unveil("/tmp/session/%sid/portal/request", "rw")); + TRY(Core::System::unveil("/tmp/session/%sid/portal/image", "rw")); + TRY(Core::System::unveil("/tmp/session/%sid/portal/websocket", "rw")); + TRY(Core::System::unveil(nullptr, nullptr)); + + Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity); + Web::Platform::FontPlugin::install(*new Web::Platform::FontPluginSerenity); + + Web::WebSockets::WebSocketClientManager::initialize(TRY(WebView::WebSocketClientManagerAdapter::try_create())); + Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create())); + TRY(Web::Bindings::initialize_main_thread_vm()); + + auto client = TRY(IPC::take_over_accepted_client_from_system_server()); + + return event_loop.exec(); +}