ExecuteScript.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. /*
  2. * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/JsonArray.h>
  7. #include <AK/JsonObject.h>
  8. #include <AK/JsonValue.h>
  9. #include <AK/NumericLimits.h>
  10. #include <AK/ScopeGuard.h>
  11. #include <AK/Time.h>
  12. #include <AK/Variant.h>
  13. #include <LibJS/Parser.h>
  14. #include <LibJS/Runtime/Array.h>
  15. #include <LibJS/Runtime/ECMAScriptFunctionObject.h>
  16. #include <LibJS/Runtime/GlobalEnvironment.h>
  17. #include <LibJS/Runtime/JSONObject.h>
  18. #include <LibJS/Runtime/Promise.h>
  19. #include <LibJS/Runtime/PromiseConstructor.h>
  20. #include <LibWeb/DOM/Document.h>
  21. #include <LibWeb/DOM/HTMLCollection.h>
  22. #include <LibWeb/DOM/NodeList.h>
  23. #include <LibWeb/FileAPI/FileList.h>
  24. #include <LibWeb/HTML/BrowsingContext.h>
  25. #include <LibWeb/HTML/HTMLOptionsCollection.h>
  26. #include <LibWeb/HTML/Scripting/Environments.h>
  27. #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
  28. #include <LibWeb/HTML/Window.h>
  29. #include <LibWeb/HTML/WindowProxy.h>
  30. #include <LibWeb/Page/Page.h>
  31. #include <LibWeb/Platform/EventLoopPlugin.h>
  32. #include <LibWeb/WebDriver/Contexts.h>
  33. #include <LibWeb/WebDriver/ElementReference.h>
  34. #include <LibWeb/WebDriver/ExecuteScript.h>
  35. namespace Web::WebDriver {
  36. #define TRY_OR_JS_ERROR(expression) \
  37. ({ \
  38. auto&& _temporary_result = (expression); \
  39. if (_temporary_result.is_error()) [[unlikely]] \
  40. return ExecuteScriptResultType::JavaScriptError; \
  41. static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
  42. "Do not return a reference from a fallible expression"); \
  43. _temporary_result.release_value(); \
  44. })
  45. static ErrorOr<JsonValue, ExecuteScriptResultType> internal_json_clone_algorithm(JS::Realm&, HTML::BrowsingContext const&, JS::Value, HashTable<JS::Object*>& seen);
  46. static ErrorOr<JsonValue, ExecuteScriptResultType> clone_an_object(JS::Realm&, HTML::BrowsingContext const&, JS::Object&, HashTable<JS::Object*>& seen, auto const& clone_algorithm);
  47. // https://w3c.github.io/webdriver/#dfn-collection
  48. static bool is_collection(JS::Object const& value)
  49. {
  50. // A collection is an Object that implements the Iterable interface, and whose:
  51. return (
  52. // - initial value of the toString own property is "Arguments"
  53. value.has_parameter_map()
  54. // - instance of Array
  55. || is<JS::Array>(value)
  56. // - instance of FileList
  57. || is<FileAPI::FileList>(value)
  58. // - instance of HTMLAllCollection
  59. || false // FIXME
  60. // - instance of HTMLCollection
  61. || is<DOM::HTMLCollection>(value)
  62. // - instance of HTMLFormControlsCollection
  63. || false // FIXME
  64. // - instance of HTMLOptionsCollection
  65. || is<HTML::HTMLOptionsCollection>(value)
  66. // - instance of NodeList
  67. || is<DOM::NodeList>(value));
  68. }
  69. // https://w3c.github.io/webdriver/#dfn-json-clone
  70. static ErrorOr<JsonValue, ExecuteScriptResultType> json_clone(JS::Realm& realm, HTML::BrowsingContext const& browsing_context, JS::Value value)
  71. {
  72. // To perform a JSON clone return the result of calling the internal JSON clone algorithm with arguments value and an empty List.
  73. auto seen = HashTable<JS::Object*> {};
  74. return internal_json_clone_algorithm(realm, browsing_context, value, seen);
  75. }
  76. // https://w3c.github.io/webdriver/#dfn-internal-json-clone-algorithm
  77. static ErrorOr<JsonValue, ExecuteScriptResultType> internal_json_clone_algorithm(JS::Realm& realm, HTML::BrowsingContext const& browsing_context, JS::Value value, HashTable<JS::Object*>& seen)
  78. {
  79. auto& vm = realm.vm();
  80. // When required to run the internal JSON clone algorithm with arguments value and seen, a remote end must return the value of the first matching statement, matching on value:
  81. // -> undefined
  82. // -> null
  83. if (value.is_nullish()) {
  84. // Success with data null.
  85. return JsonValue {};
  86. }
  87. // -> type Boolean
  88. // -> type Number
  89. // -> type String
  90. // Success with data value.
  91. if (value.is_boolean())
  92. return JsonValue { value.as_bool() };
  93. if (value.is_number())
  94. return JsonValue { value.as_double() };
  95. if (value.is_string())
  96. return JsonValue { value.as_string().byte_string() };
  97. // NOTE: BigInt and Symbol not mentioned anywhere in the WebDriver spec, as it references ES5.
  98. // It assumes that all primitives are handled above, and the value is an object for the remaining steps.
  99. if (value.is_bigint() || value.is_symbol())
  100. return ExecuteScriptResultType::JavaScriptError;
  101. // FIXME: -> a collection
  102. // -> instance of element
  103. if (value.is_object() && is<DOM::Element>(value.as_object())) {
  104. auto const& element = static_cast<DOM::Element const&>(value.as_object());
  105. // If the element is stale, return error with error code stale element reference.
  106. if (is_element_stale(element)) {
  107. return ExecuteScriptResultType::StaleElement;
  108. }
  109. // Otherwise:
  110. else {
  111. // 1. Let reference be the web element reference object for session and value.
  112. auto reference = web_element_reference_object(browsing_context, element);
  113. // 2. Return success with data reference.
  114. return reference;
  115. }
  116. }
  117. // FIXME: -> instance of shadow root
  118. // -> a WindowProxy object
  119. if (is<HTML::WindowProxy>(value.as_object())) {
  120. auto const& window_proxy = static_cast<HTML::WindowProxy&>(value.as_object());
  121. // If the associated browsing context of the WindowProxy object in value has been destroyed, return error with
  122. // error code stale element reference.
  123. if (window_proxy.associated_browsing_context()->has_navigable_been_destroyed())
  124. return ExecuteScriptResultType::BrowsingContextDiscarded;
  125. // Otherwise return success with data set to WindowProxy reference object for value.
  126. return window_proxy_reference_object(window_proxy);
  127. }
  128. // -> has an own property named "toJSON" that is a Function
  129. auto to_json = value.as_object().get_without_side_effects(vm.names.toJSON);
  130. if (to_json.is_function()) {
  131. // Return success with the value returned by Function.[[Call]](toJSON) with value as the this value.
  132. auto to_json_result = TRY_OR_JS_ERROR(to_json.as_function().internal_call(value, JS::MarkedVector<JS::Value> { vm.heap() }));
  133. if (!to_json_result.is_string())
  134. return ExecuteScriptResultType::JavaScriptError;
  135. return to_json_result.as_string().byte_string();
  136. }
  137. // -> Otherwise
  138. // 1. If value is in seen, return error with error code javascript error.
  139. if (seen.contains(&value.as_object()))
  140. return ExecuteScriptResultType::JavaScriptError;
  141. // 2. Append value to seen.
  142. seen.set(&value.as_object());
  143. ScopeGuard remove_seen { [&] {
  144. // 4. Remove the last element of seen.
  145. seen.remove(&value.as_object());
  146. } };
  147. // 3. Let result be the value of running the clone an object algorithm with arguments value and seen, and the internal JSON clone algorithm as the clone algorithm.
  148. auto result = TRY(clone_an_object(realm, browsing_context, value.as_object(), seen, internal_json_clone_algorithm));
  149. // 5. Return result.
  150. return result;
  151. }
  152. // https://w3c.github.io/webdriver/#dfn-clone-an-object
  153. static ErrorOr<JsonValue, ExecuteScriptResultType> clone_an_object(JS::Realm& realm, HTML::BrowsingContext const& browsing_context, JS::Object& value, HashTable<JS::Object*>& seen, auto const& clone_algorithm)
  154. {
  155. auto& vm = realm.vm();
  156. // 1. Let result be the value of the first matching statement, matching on value:
  157. auto get_result = [&]() -> ErrorOr<Variant<JsonArray, JsonObject>, ExecuteScriptResultType> {
  158. // -> a collection
  159. if (is_collection(value)) {
  160. // A new Array which length property is equal to the result of getting the property length of value.
  161. auto length_property = TRY_OR_JS_ERROR(value.internal_get_own_property(vm.names.length));
  162. if (!length_property->value.has_value())
  163. return ExecuteScriptResultType::JavaScriptError;
  164. auto length = TRY_OR_JS_ERROR(length_property->value->to_length(vm));
  165. if (length > NumericLimits<u32>::max())
  166. return ExecuteScriptResultType::JavaScriptError;
  167. auto array = JsonArray {};
  168. for (size_t i = 0; i < length; ++i)
  169. array.must_append(JsonValue {});
  170. return array;
  171. }
  172. // -> Otherwise
  173. else {
  174. // A new Object.
  175. return JsonObject {};
  176. }
  177. };
  178. auto result = TRY(get_result());
  179. // 2. For each enumerable own property in value, run the following substeps:
  180. for (auto& key : MUST(value.Object::internal_own_property_keys())) {
  181. // 1. Let name be the name of the property.
  182. auto name = MUST(JS::PropertyKey::from_value(vm, key));
  183. if (!value.storage_get(name)->attributes.is_enumerable())
  184. continue;
  185. // 2. Let source property value be the result of getting a property named name from value. If doing so causes script to be run and that script throws an error, return error with error code javascript error.
  186. auto source_property_value = TRY_OR_JS_ERROR(value.internal_get_own_property(name));
  187. if (!source_property_value.has_value() || !source_property_value->value.has_value())
  188. continue;
  189. // 3. Let cloned property result be the result of calling the clone algorithm with arguments source property value and seen.
  190. auto cloned_property_result = clone_algorithm(realm, browsing_context, *source_property_value->value, seen);
  191. // 4. If cloned property result is a success, set a property of result with name name and value equal to cloned property result’s data.
  192. if (!cloned_property_result.is_error()) {
  193. result.visit(
  194. [&](JsonArray& array) {
  195. // NOTE: If this was a JS array, only indexed properties would be serialized anyway.
  196. if (name.is_number())
  197. array.set(name.as_number(), cloned_property_result.value());
  198. },
  199. [&](JsonObject& object) {
  200. object.set(name.to_string(), cloned_property_result.value());
  201. });
  202. }
  203. // 5. Otherwise, return cloned property result.
  204. else {
  205. return cloned_property_result;
  206. }
  207. }
  208. return result.visit([&](auto const& value) -> JsonValue { return value; });
  209. }
  210. // https://w3c.github.io/webdriver/#dfn-execute-a-function-body
  211. static JS::ThrowCompletionOr<JS::Value> execute_a_function_body(HTML::BrowsingContext const& browsing_context, ByteString const& body, JS::MarkedVector<JS::Value> parameters)
  212. {
  213. // FIXME: If at any point during the algorithm a user prompt appears, immediately return Completion { [[Type]]: normal, [[Value]]: null, [[Target]]: empty }, but continue to run the other steps of this algorithm in parallel.
  214. // 1. Let window be the associated window of the current browsing context’s active document.
  215. auto window = browsing_context.active_document()->window();
  216. // 2. Let environment settings be the environment settings object for window.
  217. auto& environment_settings = Web::HTML::relevant_settings_object(*window);
  218. // 3. Let global scope be environment settings realm’s global environment.
  219. auto& global_scope = environment_settings.realm().global_environment();
  220. auto& realm = window->realm();
  221. auto source_text = ByteString::formatted("function() {{ {} }}", body);
  222. auto parser = JS::Parser { JS::Lexer { source_text } };
  223. auto function_expression = parser.parse_function_node<JS::FunctionExpression>();
  224. // 4. If body is not parsable as a FunctionBody or if parsing detects an early error, return Completion { [[Type]]: normal, [[Value]]: null, [[Target]]: empty }.
  225. if (parser.has_errors())
  226. return JS::js_null();
  227. // 5. If body begins with a directive prologue that contains a use strict directive then let strict be true, otherwise let strict be false.
  228. // NOTE: Handled in step 8 below.
  229. // 6. Prepare to run a script with environment settings.
  230. environment_settings.prepare_to_run_script();
  231. // 7. Prepare to run a callback with environment settings.
  232. environment_settings.prepare_to_run_callback();
  233. // 8. Let function be the result of calling FunctionCreate, with arguments:
  234. // kind
  235. // Normal.
  236. // list
  237. // An empty List.
  238. // body
  239. // The result of parsing body above.
  240. // global scope
  241. // The result of parsing global scope above.
  242. // strict
  243. // The result of parsing strict above.
  244. 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());
  245. // 9. Let completion be Function.[[Call]](window, parameters) with function as the this value.
  246. // NOTE: This is not entirely clear, but I don't think they mean actually passing `function` as
  247. // the this value argument, but using it as the object [[Call]] is executed on.
  248. auto completion = function->internal_call(window, move(parameters));
  249. // 10. Clean up after running a callback with environment settings.
  250. environment_settings.clean_up_after_running_callback();
  251. // 11. Clean up after running a script with environment settings.
  252. environment_settings.clean_up_after_running_script();
  253. // 12. Return completion.
  254. return completion;
  255. }
  256. class HeapTimer : public JS::Cell {
  257. JS_CELL(HeapTimer, JS::Cell);
  258. JS_DECLARE_ALLOCATOR(HeapTimer);
  259. public:
  260. explicit HeapTimer()
  261. : m_timer(Core::Timer::create())
  262. {
  263. }
  264. virtual ~HeapTimer() override = default;
  265. void start(u64 timeout_ms, JS::NonnullGCPtr<OnScriptComplete> on_timeout)
  266. {
  267. m_on_timeout = on_timeout;
  268. m_timer->on_timeout = [this]() {
  269. m_timed_out = true;
  270. if (m_on_timeout) {
  271. auto error_object = JsonObject {};
  272. error_object.set("name", "Error");
  273. error_object.set("message", "Script Timeout");
  274. m_on_timeout->function()({ ExecuteScriptResultType::Timeout, move(error_object) });
  275. m_on_timeout = nullptr;
  276. }
  277. };
  278. m_timer->set_interval(static_cast<int>(timeout_ms));
  279. m_timer->set_single_shot(true);
  280. m_timer->start();
  281. }
  282. void stop()
  283. {
  284. m_on_timeout = nullptr;
  285. m_timer->stop();
  286. }
  287. bool is_timed_out() const { return m_timed_out; }
  288. private:
  289. virtual void visit_edges(JS::Cell::Visitor& visitor) override
  290. {
  291. Base::visit_edges(visitor);
  292. visitor.visit(m_on_timeout);
  293. }
  294. NonnullRefPtr<Core::Timer> m_timer;
  295. JS::GCPtr<OnScriptComplete> m_on_timeout;
  296. bool m_timed_out { false };
  297. };
  298. JS_DEFINE_ALLOCATOR(HeapTimer);
  299. void execute_script(HTML::BrowsingContext const& browsing_context, ByteString body, JS::MarkedVector<JS::Value> arguments, Optional<u64> const& timeout_ms, JS::NonnullGCPtr<OnScriptComplete> on_complete)
  300. {
  301. auto const* document = browsing_context.active_document();
  302. auto& realm = document->realm();
  303. auto& vm = document->vm();
  304. // 5. Let timer be a new timer.
  305. auto timer = vm.heap().allocate<HeapTimer>(realm);
  306. // 6. If timeout is not null:
  307. if (timeout_ms.has_value()) {
  308. // 1. Start the timer with timer and timeout.
  309. timer->start(timeout_ms.value(), on_complete);
  310. }
  311. // AD-HOC: An execution context is required for Promise creation hooks.
  312. HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
  313. // 7. Let promise be a new Promise.
  314. auto promise_capability = WebIDL::create_promise(realm);
  315. JS::NonnullGCPtr promise { verify_cast<JS::Promise>(*promise_capability->promise()) };
  316. // 8. Run the following substeps in parallel:
  317. Platform::EventLoopPlugin::the().deferred_invoke([&realm, &browsing_context, promise_capability, document, promise, body = move(body), arguments = move(arguments)]() mutable {
  318. HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object() };
  319. // 1. Let scriptPromise be the result of promise-calling execute a function body, with arguments body and arguments.
  320. auto script_result = execute_a_function_body(browsing_context, body, move(arguments));
  321. // 2. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
  322. if (script_result.has_value()) {
  323. WebIDL::resolve_promise(realm, promise_capability, script_result.release_value());
  324. }
  325. // 3. Upon rejection of scriptPromise with value r, reject promise with value r.
  326. if (script_result.is_throw_completion()) {
  327. promise->reject(*script_result.throw_completion().value());
  328. }
  329. });
  330. // 9. Wait until promise is resolved, or timer's timeout fired flag is set, whichever occurs first.
  331. auto reaction_steps = JS::create_heap_function(vm.heap(), [&realm, &browsing_context, promise, timer, on_complete](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
  332. if (timer->is_timed_out())
  333. return JS::js_undefined();
  334. timer->stop();
  335. auto json_value_or_error = json_clone(realm, browsing_context, promise->result());
  336. if (json_value_or_error.is_error()) {
  337. auto error_object = JsonObject {};
  338. error_object.set("name", "Error");
  339. error_object.set("message", "Could not clone result value");
  340. on_complete->function()({ ExecuteScriptResultType::JavaScriptError, move(error_object) });
  341. }
  342. // 10. If promise is still pending and timer's timeout fired flag is set, return error with error code script timeout.
  343. // NOTE: This is handled by the HeapTimer.
  344. // 11. If promise is fulfilled with value v, let result be JSON clone with session and v, and return success with data result.
  345. else if (promise->state() == JS::Promise::State::Fulfilled) {
  346. on_complete->function()({ ExecuteScriptResultType::PromiseResolved, json_value_or_error.release_value() });
  347. }
  348. // 12. If promise is rejected with reason r, let result be JSON clone with session and r, and return error with error code javascript error and data result.
  349. else if (promise->state() == JS::Promise::State::Rejected) {
  350. on_complete->function()({ ExecuteScriptResultType::PromiseRejected, json_value_or_error.release_value() });
  351. }
  352. return JS::js_undefined();
  353. });
  354. WebIDL::react_to_promise(promise_capability, reaction_steps, reaction_steps);
  355. }
  356. void execute_async_script(HTML::BrowsingContext const& browsing_context, ByteString body, JS::MarkedVector<JS::Value> arguments, Optional<u64> const& timeout_ms, JS::NonnullGCPtr<OnScriptComplete> on_complete)
  357. {
  358. auto const* document = browsing_context.active_document();
  359. auto& realm = document->realm();
  360. auto& vm = document->vm();
  361. // 5. Let timer be a new timer.
  362. auto timer = vm.heap().allocate<HeapTimer>(realm);
  363. // 6. If timeout is not null:
  364. if (timeout_ms.has_value()) {
  365. // 1. Start the timer with timer and timeout.
  366. timer->start(timeout_ms.value(), on_complete);
  367. }
  368. // AD-HOC: An execution context is required for Promise creation hooks.
  369. HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
  370. // 7. Let promise be a new Promise.
  371. auto promise_capability = WebIDL::create_promise(realm);
  372. JS::NonnullGCPtr promise { verify_cast<JS::Promise>(*promise_capability->promise()) };
  373. // 8. Run the following substeps in parallel:
  374. Platform::EventLoopPlugin::the().deferred_invoke([&vm, &realm, &browsing_context, timer, document, promise_capability, promise, body = move(body), arguments = move(arguments)]() mutable {
  375. HTML::TemporaryExecutionContext execution_context { document->relevant_settings_object() };
  376. // 1. Let resolvingFunctions be CreateResolvingFunctions(promise).
  377. auto resolving_functions = promise->create_resolving_functions();
  378. // 2. Append resolvingFunctions.[[Resolve]] to arguments.
  379. arguments.append(resolving_functions.resolve);
  380. // 3. Let result be the result of calling execute a function body, with arguments body and arguments.
  381. // FIXME: 'result' -> 'scriptResult' (spec issue)
  382. auto script_result = execute_a_function_body(browsing_context, body, move(arguments));
  383. // 4. If scriptResult.[[Type]] is not normal, then reject promise with value scriptResult.[[Value]], and abort these steps.
  384. // NOTE: Prior revisions of this specification did not recognize the return value of the provided script.
  385. // In order to preserve legacy behavior, the return value only influences the command if it is a
  386. // "thenable" object or if determining this produces an exception.
  387. if (script_result.is_throw_completion()) {
  388. promise->reject(*script_result.throw_completion().value());
  389. return;
  390. }
  391. // 5. If Type(scriptResult.[[Value]]) is not Object, then abort these steps.
  392. if (!script_result.value().is_object())
  393. return;
  394. // 6. Let then be Get(scriptResult.[[Value]], "then").
  395. auto then = script_result.value().as_object().get(vm.names.then);
  396. // 7. If then.[[Type]] is not normal, then reject promise with value then.[[Value]], and abort these steps.
  397. if (then.is_throw_completion()) {
  398. promise->reject(*then.throw_completion().value());
  399. return;
  400. }
  401. // 8. If IsCallable(then.[[Type]]) is false, then abort these steps.
  402. if (!then.value().is_function())
  403. return;
  404. // 9. Let scriptPromise be PromiseResolve(Promise, scriptResult.[[Value]]).
  405. auto script_promise_or_error = JS::promise_resolve(vm, realm.intrinsics().promise_constructor(), script_result.value());
  406. if (script_promise_or_error.is_throw_completion())
  407. return;
  408. auto& script_promise = static_cast<JS::Promise&>(*script_promise_or_error.value());
  409. vm.custom_data()->spin_event_loop_until([&] {
  410. return timer->is_timed_out() || script_promise.state() != JS::Promise::State::Pending;
  411. });
  412. // 10. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
  413. if (script_promise.state() == JS::Promise::State::Fulfilled)
  414. WebIDL::resolve_promise(realm, promise_capability, script_promise.result());
  415. // 11. Upon rejection of scriptPromise with value r, reject promise with value r.
  416. if (script_promise.state() == JS::Promise::State::Rejected)
  417. WebIDL::reject_promise(realm, promise_capability, script_promise.result());
  418. });
  419. // 9. Wait until promise is resolved, or timer's timeout fired flag is set, whichever occurs first.
  420. auto reaction_steps = JS::create_heap_function(vm.heap(), [&realm, &browsing_context, promise, timer, on_complete](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
  421. if (timer->is_timed_out())
  422. return JS::js_undefined();
  423. timer->stop();
  424. auto json_value_or_error = json_clone(realm, browsing_context, promise->result());
  425. if (json_value_or_error.is_error()) {
  426. auto error_object = JsonObject {};
  427. error_object.set("name", "Error");
  428. error_object.set("message", "Could not clone result value");
  429. on_complete->function()({ ExecuteScriptResultType::JavaScriptError, move(error_object) });
  430. }
  431. // 10. If promise is still pending and timer's timeout fired flag is set, return error with error code script timeout.
  432. // NOTE: This is handled by the HeapTimer.
  433. // 11. If promise is fulfilled with value v, let result be JSON clone with session and v, and return success with data result.
  434. else if (promise->state() == JS::Promise::State::Fulfilled) {
  435. on_complete->function()({ ExecuteScriptResultType::PromiseResolved, json_value_or_error.release_value() });
  436. }
  437. // 12. If promise is rejected with reason r, let result be JSON clone with session and r, and return error with error code javascript error and data result.
  438. else if (promise->state() == JS::Promise::State::Rejected) {
  439. on_complete->function()({ ExecuteScriptResultType::PromiseRejected, json_value_or_error.release_value() });
  440. }
  441. return JS::js_undefined();
  442. });
  443. WebIDL::react_to_promise(promise_capability, reaction_steps, reaction_steps);
  444. }
  445. }