DedicatedWorkerHost.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /*
  2. * Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Runtime/ConsoleObject.h>
  7. #include <LibWeb/Fetch/Fetching/Fetching.h>
  8. #include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
  9. #include <LibWeb/HTML/Scripting/ClassicScript.h>
  10. #include <LibWeb/HTML/Scripting/Fetching.h>
  11. #include <LibWeb/HTML/Scripting/WorkerEnvironmentSettingsObject.h>
  12. #include <LibWeb/HTML/WorkerDebugConsoleClient.h>
  13. #include <LibWeb/HTML/WorkerGlobalScope.h>
  14. #include <LibWeb/Loader/ResourceLoader.h>
  15. #include <WebWorker/DedicatedWorkerHost.h>
  16. namespace WebWorker {
  17. DedicatedWorkerHost::DedicatedWorkerHost(Web::Page& page, AK::URL url, String type)
  18. : m_worker_vm(JS::VM::create(make<Web::Bindings::WebEngineCustomData>()).release_value_but_fixme_should_propagate_errors())
  19. , m_page(page)
  20. , m_url(move(url))
  21. , m_type(move(type))
  22. {
  23. // FIXME: We need to attach all the HostDefined hooks from MainThreadVM onto this VM in order to load
  24. // module scripts in Workers.
  25. }
  26. DedicatedWorkerHost::~DedicatedWorkerHost() = default;
  27. // https://html.spec.whatwg.org/multipage/workers.html#run-a-worker
  28. // FIXME: Extract out into a helper for both shared and dedicated workers
  29. void DedicatedWorkerHost::run()
  30. {
  31. bool const is_shared = false;
  32. // 7. Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations:
  33. auto realm_execution_context = Web::Bindings::create_a_new_javascript_realm(
  34. *m_worker_vm,
  35. [this](JS::Realm& realm) -> JS::Object* {
  36. // 7a. For the global object, if is shared is true, create a new SharedWorkerGlobalScope object.
  37. // 7b. Otherwise, create a new DedicatedWorkerGlobalScope object.
  38. // FIXME: Proper support for both SharedWorkerGlobalScope and DedicatedWorkerGlobalScope
  39. if (is_shared)
  40. TODO();
  41. return m_worker_vm->heap().allocate_without_realm<Web::HTML::WorkerGlobalScope>(realm, m_page);
  42. },
  43. nullptr);
  44. // 8. Let worker global scope be the global object of realm execution context's Realm component.
  45. // NOTE: This is the DedicatedWorkerGlobalScope or SharedWorkerGlobalScope object created in the previous step.
  46. JS::NonnullGCPtr<Web::HTML::WorkerGlobalScope> worker_global_scope = verify_cast<Web::HTML::WorkerGlobalScope>(realm_execution_context->realm->global_object());
  47. // 9. Set up a worker environment settings object with realm execution context,
  48. // outside settings, and unsafeWorkerCreationTime, and let inside settings be the result.
  49. auto inner_settings = Web::HTML::WorkerEnvironmentSettingsObject::setup(move(realm_execution_context));
  50. inner_settings->responsible_event_loop().set_vm(*m_worker_vm);
  51. auto& console_object = *inner_settings->realm().intrinsics().console_object();
  52. m_console = adopt_ref(*new Web::HTML::WorkerDebugConsoleClient(console_object.console()));
  53. VERIFY(m_console);
  54. console_object.console().set_client(*m_console);
  55. // 10. Set worker global scope's name to the value of options's name member.
  56. // FIXME: name property requires the SharedWorkerGlobalScope or DedicatedWorkerGlobalScope child class to be used
  57. // 11. Append owner to worker global scope's owner set.
  58. // FIXME: support for 'owner' set on WorkerGlobalScope
  59. // 12. If is shared is true, then:
  60. if (is_shared) {
  61. // FIXME: Shared worker support
  62. // 1. Set worker global scope's constructor origin to outside settings's origin.
  63. // 2. Set worker global scope's constructor url to url.
  64. // 3. Set worker global scope's type to the value of options's type member.
  65. // 4. Set worker global scope's credentials to the value of options's credentials member.
  66. }
  67. // 13. Let destination be "sharedworker" if is shared is true, and "worker" otherwise.
  68. auto destination = is_shared ? Web::Fetch::Infrastructure::Request::Destination::SharedWorker
  69. : Web::Fetch::Infrastructure::Request::Destination::Worker;
  70. // In both cases, let performFetch be the following perform the fetch hook given request, isTopLevel and processCustomFetchResponse:
  71. auto perform_fetch_function = [inner_settings, worker_global_scope](JS::NonnullGCPtr<Web::Fetch::Infrastructure::Request> request, Web::HTML::IsTopLevel is_top_level, Web::Fetch::Infrastructure::FetchAlgorithms::ProcessResponseConsumeBodyFunction process_custom_fetch_response) -> Web::WebIDL::ExceptionOr<void> {
  72. auto& realm = inner_settings->realm();
  73. auto& vm = realm.vm();
  74. Web::Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
  75. // 1. If isTopLevel is false, fetch request with processResponseConsumeBody set to processCustomFetchResponse, and abort these steps.
  76. if (is_top_level == Web::HTML::IsTopLevel::No) {
  77. fetch_algorithms_input.process_response_consume_body = move(process_custom_fetch_response);
  78. TRY(Web::Fetch::Fetching::fetch(realm, request, Web::Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))));
  79. return {};
  80. }
  81. // 2. Set request's reserved client to inside settings.
  82. request->set_reserved_client(JS::GCPtr<Web::HTML::EnvironmentSettingsObject>(inner_settings));
  83. // We need to store the process custom fetch response function on the heap here, because we're storing it in another heap function
  84. auto process_custom_fetch_response_function = JS::create_heap_function(vm.heap(), move(process_custom_fetch_response));
  85. // 3. Fetch request with processResponseConsumeBody set to the following steps given response response and null, failure, or a byte sequence bodyBytes:
  86. fetch_algorithms_input.process_response_consume_body = [worker_global_scope, process_custom_fetch_response_function](auto response, auto body_bytes) {
  87. // 1. Set worker global scope's url to response's url.
  88. worker_global_scope->set_url(response->url().value_or({}));
  89. // FIXME: 2. Initialize worker global scope's policy container given worker global scope, response, and inside settings.
  90. // FIXME: 3. If the Run CSP initialization for a global object algorithm returns "Blocked" when executed upon worker
  91. // global scope, set response to a network error. [CSP]
  92. // FIXME: 4. If worker global scope's embedder policy's value is compatible with cross-origin isolation and is shared is true,
  93. // then set agent's agent cluster's cross-origin isolation mode to "logical" or "concrete".
  94. // The one chosen is implementation-defined.
  95. // FIXME: 5. If the result of checking a global object's embedder policy with worker global scope, outside settings,
  96. // and response is false, then set response to a network error.
  97. // FIXME: 6. Set worker global scope's cross-origin isolated capability to true if agent's agent cluster's cross-origin
  98. // isolation mode is "concrete".
  99. if (!is_shared) {
  100. // 7. If is shared is false and owner's cross-origin isolated capability is false, then set worker
  101. // global scope's cross-origin isolated capability to false.
  102. // 8. If is shared is false and response's url's scheme is "data", then set worker global scope's
  103. // cross-origin isolated capability to false.
  104. }
  105. // 9. Run processCustomFetchResponse with response and bodyBytes.
  106. process_custom_fetch_response_function->function()(response, body_bytes);
  107. };
  108. TRY(Web::Fetch::Fetching::fetch(realm, request, Web::Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input))));
  109. return {};
  110. };
  111. auto perform_fetch = Web::HTML::create_perform_the_fetch_hook(inner_settings->heap(), move(perform_fetch_function));
  112. auto on_complete_function = [inner_settings, worker_global_scope](JS::GCPtr<Web::HTML::Script> script) {
  113. auto& realm = inner_settings->realm();
  114. // 1. If script is null or if script's error to rethrow is non-null, then:
  115. if (!script || !script->error_to_rethrow().is_null()) {
  116. // 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.
  117. // FIXME: Notify Worker parent through IPC to fire an error event at Worker
  118. // FIXME 2. Run the environment discarding steps for inside settings.
  119. // 3. Abort these steps.
  120. dbgln("Didn't get my script :(");
  121. return;
  122. }
  123. // FIXME: 2. Associate worker with worker global scope.
  124. // What does this even mean?
  125. // FIXME: 3. Let inside port be a new MessagePort object in inside settings's Realm.
  126. // FIXME: 4. Associate inside port with worker global scope.
  127. // FIXME: 5. Entangle outside port and inside port.
  128. // 6. Create a new WorkerLocation object and associate it with worker global scope.
  129. worker_global_scope->set_location(realm.heap().allocate<Web::HTML::WorkerLocation>(realm, *worker_global_scope));
  130. // FIXME: 7. Closing orphan workers: Start monitoring the worker such that no sooner than it
  131. // stops being a protected worker, and no later than it stops being a permissible worker,
  132. // worker global scope's closing flag is set to true.
  133. // FIXME: 8. Suspending workers: Start monitoring the worker, such that whenever worker global scope's
  134. // closing flag is false and the worker is a suspendable worker, the user agent suspends
  135. // execution of script in that worker until such time as either the closing flag switches to
  136. // true or the worker stops being a suspendable worker
  137. // 9. Set inside settings's execution ready flag.
  138. inner_settings->execution_ready = true;
  139. // 10. If script is a classic script, then run the classic script script.
  140. // Otherwise, it is a module script; run the module script script.
  141. if (is<Web::HTML::ClassicScript>(*script))
  142. (void)static_cast<Web::HTML::ClassicScript&>(*script).run();
  143. else
  144. (void)verify_cast<Web::HTML::JavaScriptModuleScript>(*script).run();
  145. // FIXME: 11. Enable outside port's port message queue.
  146. // FIXME: 12. If is shared is false, enable the port message queue of the worker's implicit port.
  147. // FIXME: 13. If is shared is true, then queue a global task on DOM manipulation task source given worker
  148. // global scope to fire an event named connect at worker global scope, using MessageEvent,
  149. // with the data attribute initialized to the empty string, the ports attribute initialized
  150. // to a new frozen array containing inside port, and the source attribute initialized to inside port.
  151. // FIXME: 14. Enable the client message queue of the ServiceWorkerContainer object whose associated service
  152. // worker client is worker global scope's relevant settings object.
  153. // 15. Event loop: Run the responsible event loop specified by inside settings until it is destroyed.
  154. inner_settings->responsible_event_loop().schedule();
  155. // FIXME: We need to react to the closing flag being set on the responsible event loop
  156. // And use that to shutdown the WorkerHost
  157. // FIXME: 16. Clear the worker global scope's map of active timers.
  158. // FIXME: 17. Disentangle all the ports in the list of the worker's ports.
  159. // FIXME: 18. Empty worker global scope's owner set.
  160. };
  161. auto on_complete = Web::HTML::create_on_fetch_script_complete(inner_settings->vm().heap(), move(on_complete_function));
  162. // 14. Obtain script by switching on the value of options's type member:
  163. // classic: Fetch a classic worker script given url, outside settings, destination, inside settings,
  164. // and with onComplete and performFetch as defined below.
  165. // module: Fetch a module worker script graph given url, outside settings, destination, the value of the credentials member of options, inside settings,
  166. // and with onComplete and performFetch as defined below.
  167. if (m_type != "classic"sv) {
  168. dbgln("Unsupported script type {} for LibWeb/Worker", m_type);
  169. TODO();
  170. }
  171. // FIXME: We don't have outside settings anymore, they live in the owner. https://github.com/whatwg/html/issues/9920
  172. if (auto err = Web::HTML::fetch_classic_worker_script(m_url, inner_settings, destination, inner_settings, perform_fetch, on_complete); err.is_error()) {
  173. dbgln("Failed to run worker script");
  174. // FIXME: Abort the worker properly
  175. TODO();
  176. }
  177. }
  178. }