ExecuteScript.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. /*
  2. * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
  3. * Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibJS/Parser.h>
  8. #include <LibJS/Runtime/ECMAScriptFunctionObject.h>
  9. #include <LibJS/Runtime/GlobalEnvironment.h>
  10. #include <LibJS/Runtime/ObjectEnvironment.h>
  11. #include <LibJS/Runtime/PromiseConstructor.h>
  12. #include <LibWeb/DOM/Document.h>
  13. #include <LibWeb/HTML/BrowsingContext.h>
  14. #include <LibWeb/HTML/Scripting/Environments.h>
  15. #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
  16. #include <LibWeb/HTML/Window.h>
  17. #include <LibWeb/Platform/EventLoopPlugin.h>
  18. #include <LibWeb/WebDriver/ExecuteScript.h>
  19. #include <LibWeb/WebDriver/HeapTimer.h>
  20. namespace Web::WebDriver {
  21. // https://w3c.github.io/webdriver/#dfn-execute-a-function-body
  22. static JS::ThrowCompletionOr<JS::Value> execute_a_function_body(HTML::BrowsingContext const& browsing_context, ByteString const& body, ReadonlySpan<JS::Value> parameters)
  23. {
  24. // 1. Let window be the associated window of the current browsing context’s active document.
  25. auto window = browsing_context.active_document()->window();
  26. // 2. Let environment settings be the environment settings object for window.
  27. auto& environment_settings = Web::HTML::relevant_settings_object(*window);
  28. // 3. Let global scope be environment settings realm’s global environment.
  29. auto& realm = environment_settings.realm();
  30. auto& global_scope = realm.global_environment();
  31. auto source_text = ByteString::formatted(
  32. R"~~~(function() {{
  33. {}
  34. }})~~~",
  35. body);
  36. auto parser = JS::Parser { JS::Lexer { source_text } };
  37. auto function_expression = parser.parse_function_node<JS::FunctionExpression>();
  38. // 4. If body is not parsable as a FunctionBody or if parsing detects an early error, return Completion { [[Type]]: normal, [[Value]]: null, [[Target]]: empty }.
  39. if (parser.has_errors())
  40. return JS::js_null();
  41. // 5. If body begins with a directive prologue that contains a use strict directive then let strict be true, otherwise let strict be false.
  42. // NOTE: Handled in step 8 below.
  43. // 6. Prepare to run a script with realm.
  44. HTML::prepare_to_run_script(realm);
  45. // 7. Prepare to run a callback with environment settings.
  46. HTML::prepare_to_run_callback(realm);
  47. // 8. Let function be the result of calling FunctionCreate, with arguments:
  48. // kind
  49. // Normal.
  50. // list
  51. // An empty List.
  52. // body
  53. // The result of parsing body above.
  54. // global scope
  55. // The result of parsing global scope above.
  56. // strict
  57. // The result of parsing strict above.
  58. auto function = JS::ECMAScriptFunctionObject::create(realm, "", move(source_text), function_expression->body(), function_expression->parameters(), function_expression->function_length(), function_expression->local_variables_names(), &global_scope, nullptr, function_expression->kind(), function_expression->is_strict_mode(), function_expression->parsing_insights());
  59. // 9. Let completion be Function.[[Call]](window, parameters) with function as the this value.
  60. // NOTE: This is not entirely clear, but I don't think they mean actually passing `function` as
  61. // the this value argument, but using it as the object [[Call]] is executed on.
  62. auto completion = function->internal_call(window, parameters);
  63. // 10. Clean up after running a callback with environment settings.
  64. HTML::clean_up_after_running_callback(realm);
  65. // 11. Clean up after running a script with realm.
  66. HTML::clean_up_after_running_script(realm);
  67. // 12. Return completion.
  68. return completion;
  69. }
  70. void execute_script(HTML::BrowsingContext const& browsing_context, ByteString body, GC::MarkedVector<JS::Value> arguments, Optional<u64> const& timeout_ms, GC::Ref<OnScriptComplete> on_complete)
  71. {
  72. auto const* document = browsing_context.active_document();
  73. auto& realm = document->realm();
  74. auto& vm = document->vm();
  75. // 5. Let timer be a new timer.
  76. auto timer = realm.create<HeapTimer>();
  77. // 6. If timeout is not null:
  78. if (timeout_ms.has_value()) {
  79. // 1. Start the timer with timer and timeout.
  80. timer->start(timeout_ms.value(), GC::create_function(vm.heap(), [on_complete]() {
  81. on_complete->function()({ .state = JS::Promise::State::Pending });
  82. }));
  83. }
  84. // AD-HOC: An execution context is required for Promise creation hooks.
  85. HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
  86. // 7. Let promise be a new Promise.
  87. auto promise = WebIDL::create_promise(realm);
  88. // 8. Run the following substeps in parallel:
  89. Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, &browsing_context, promise, body = move(body), arguments = move(arguments)]() mutable {
  90. HTML::TemporaryExecutionContext execution_context { realm };
  91. // 1. Let scriptPromise be the result of promise-calling execute a function body, with arguments body and arguments.
  92. auto script_result = execute_a_function_body(browsing_context, body, move(arguments));
  93. // FIXME: This isn't right, we should be reacting to this using WebIDL::react_to_promise()
  94. // 2. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
  95. if (script_result.has_value()) {
  96. WebIDL::resolve_promise(realm, promise, script_result.release_value());
  97. }
  98. // 3. Upon rejection of scriptPromise with value r, reject promise with value r.
  99. if (script_result.is_throw_completion()) {
  100. WebIDL::reject_promise(realm, promise, *script_result.throw_completion().value());
  101. }
  102. }));
  103. // 9. Wait until promise is resolved, or timer's timeout fired flag is set, whichever occurs first.
  104. auto reaction_steps = GC::create_function(vm.heap(), [promise, timer, on_complete](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
  105. if (timer->is_timed_out())
  106. return JS::js_undefined();
  107. timer->stop();
  108. auto promise_promise = GC::Ref { verify_cast<JS::Promise>(*promise->promise()) };
  109. on_complete->function()({ promise_promise->state(), promise_promise->result() });
  110. return JS::js_undefined();
  111. });
  112. WebIDL::react_to_promise(promise, reaction_steps, reaction_steps);
  113. }
  114. void execute_async_script(HTML::BrowsingContext const& browsing_context, ByteString body, GC::MarkedVector<JS::Value> arguments, Optional<u64> const& timeout_ms, GC::Ref<OnScriptComplete> on_complete)
  115. {
  116. auto const* document = browsing_context.active_document();
  117. auto& realm = document->realm();
  118. auto& vm = document->vm();
  119. // 5. Let timer be a new timer.
  120. auto timer = realm.create<HeapTimer>();
  121. // 6. If timeout is not null:
  122. if (timeout_ms.has_value()) {
  123. // 1. Start the timer with timer and timeout.
  124. timer->start(timeout_ms.value(), GC::create_function(vm.heap(), [on_complete]() {
  125. on_complete->function()({ .state = JS::Promise::State::Pending });
  126. }));
  127. }
  128. // AD-HOC: An execution context is required for Promise creation hooks.
  129. HTML::TemporaryExecutionContext execution_context { realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
  130. // 7. Let promise be a new Promise.
  131. auto promise_capability = WebIDL::create_promise(realm);
  132. GC::Ref promise { verify_cast<JS::Promise>(*promise_capability->promise()) };
  133. // 8. Run the following substeps in parallel:
  134. Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&vm, &realm, &browsing_context, timer, promise_capability, promise, body = move(body), arguments = move(arguments)]() mutable {
  135. HTML::TemporaryExecutionContext execution_context { realm };
  136. // 1. Let resolvingFunctions be CreateResolvingFunctions(promise).
  137. auto resolving_functions = promise->create_resolving_functions();
  138. // 2. Append resolvingFunctions.[[Resolve]] to arguments.
  139. arguments.append(resolving_functions.resolve);
  140. // 3. Let result be the result of calling execute a function body, with arguments body and arguments.
  141. // FIXME: 'result' -> 'scriptResult' (spec issue)
  142. auto script_result = execute_a_function_body(browsing_context, body, move(arguments));
  143. // 4. If scriptResult.[[Type]] is not normal, then reject promise with value scriptResult.[[Value]], and abort these steps.
  144. // NOTE: Prior revisions of this specification did not recognize the return value of the provided script.
  145. // In order to preserve legacy behavior, the return value only influences the command if it is a
  146. // "thenable" object or if determining this produces an exception.
  147. if (script_result.is_throw_completion()) {
  148. promise->reject(*script_result.throw_completion().value());
  149. return;
  150. }
  151. // 5. If Type(scriptResult.[[Value]]) is not Object, then abort these steps.
  152. if (!script_result.value().is_object())
  153. return;
  154. // 6. Let then be Get(scriptResult.[[Value]], "then").
  155. auto then = script_result.value().as_object().get(vm.names.then);
  156. // 7. If then.[[Type]] is not normal, then reject promise with value then.[[Value]], and abort these steps.
  157. if (then.is_throw_completion()) {
  158. promise->reject(*then.throw_completion().value());
  159. return;
  160. }
  161. // 8. If IsCallable(then.[[Type]]) is false, then abort these steps.
  162. if (!then.value().is_function())
  163. return;
  164. // 9. Let scriptPromise be PromiseResolve(Promise, scriptResult.[[Value]]).
  165. auto script_promise_or_error = JS::promise_resolve(vm, realm.intrinsics().promise_constructor(), script_result.value());
  166. if (script_promise_or_error.is_throw_completion())
  167. return;
  168. auto& script_promise = static_cast<JS::Promise&>(*script_promise_or_error.value());
  169. vm.custom_data()->spin_event_loop_until(GC::create_function(vm.heap(), [timer, &script_promise]() {
  170. return timer->is_timed_out() || script_promise.state() != JS::Promise::State::Pending;
  171. }));
  172. // 10. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
  173. if (script_promise.state() == JS::Promise::State::Fulfilled)
  174. WebIDL::resolve_promise(realm, promise_capability, script_promise.result());
  175. // 11. Upon rejection of scriptPromise with value r, reject promise with value r.
  176. if (script_promise.state() == JS::Promise::State::Rejected)
  177. WebIDL::reject_promise(realm, promise_capability, script_promise.result());
  178. }));
  179. // 9. Wait until promise is resolved, or timer's timeout fired flag is set, whichever occurs first.
  180. auto reaction_steps = GC::create_function(vm.heap(), [promise, timer, on_complete](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
  181. if (timer->is_timed_out())
  182. return JS::js_undefined();
  183. timer->stop();
  184. on_complete->function()({ promise->state(), promise->result() });
  185. return JS::js_undefined();
  186. });
  187. WebIDL::react_to_promise(promise_capability, reaction_steps, reaction_steps);
  188. }
  189. }