ExecuteScript.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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/Window.h>
  28. #include <LibWeb/Page/Page.h>
  29. #include <LibWeb/WebDriver/Contexts.h>
  30. #include <LibWeb/WebDriver/ExecuteScript.h>
  31. namespace Web::WebDriver {
  32. #define TRY_OR_JS_ERROR(expression) \
  33. ({ \
  34. auto&& _temporary_result = (expression); \
  35. if (_temporary_result.is_error()) [[unlikely]] \
  36. return ExecuteScriptResultType::JavaScriptError; \
  37. static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
  38. "Do not return a reference from a fallible expression"); \
  39. _temporary_result.release_value(); \
  40. })
  41. static ErrorOr<JsonValue, ExecuteScriptResultType> internal_json_clone_algorithm(JS::Realm&, JS::Value, HashTable<JS::Object*>& seen);
  42. static ErrorOr<JsonValue, ExecuteScriptResultType> clone_an_object(JS::Realm&, JS::Object&, HashTable<JS::Object*>& seen, auto const& clone_algorithm);
  43. // https://w3c.github.io/webdriver/#dfn-collection
  44. static bool is_collection(JS::Object const& value)
  45. {
  46. // A collection is an Object that implements the Iterable interface, and whose:
  47. return (
  48. // - initial value of the toString own property is "Arguments"
  49. value.has_parameter_map()
  50. // - instance of Array
  51. || is<JS::Array>(value)
  52. // - instance of FileList
  53. || is<FileAPI::FileList>(value)
  54. // - instance of HTMLAllCollection
  55. || false // FIXME
  56. // - instance of HTMLCollection
  57. || is<DOM::HTMLCollection>(value)
  58. // - instance of HTMLFormControlsCollection
  59. || false // FIXME
  60. // - instance of HTMLOptionsCollection
  61. || is<HTML::HTMLOptionsCollection>(value)
  62. // - instance of NodeList
  63. || is<DOM::NodeList>(value));
  64. }
  65. // https://w3c.github.io/webdriver/#dfn-json-clone
  66. static ErrorOr<JsonValue, ExecuteScriptResultType> json_clone(JS::Realm& realm, JS::Value value)
  67. {
  68. // To perform a JSON clone return the result of calling the internal JSON clone algorithm with arguments value and an empty List.
  69. auto seen = HashTable<JS::Object*> {};
  70. return internal_json_clone_algorithm(realm, value, seen);
  71. }
  72. // https://w3c.github.io/webdriver/#dfn-internal-json-clone-algorithm
  73. static ErrorOr<JsonValue, ExecuteScriptResultType> internal_json_clone_algorithm(JS::Realm& realm, JS::Value value, HashTable<JS::Object*>& seen)
  74. {
  75. auto& vm = realm.vm();
  76. // 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:
  77. // -> undefined
  78. // -> null
  79. if (value.is_nullish()) {
  80. // Success with data null.
  81. return JsonValue {};
  82. }
  83. // -> type Boolean
  84. // -> type Number
  85. // -> type String
  86. // Success with data value.
  87. if (value.is_boolean())
  88. return JsonValue { value.as_bool() };
  89. if (value.is_number())
  90. return JsonValue { value.as_double() };
  91. if (value.is_string())
  92. return JsonValue { TRY_OR_JS_ERROR(value.as_string().deprecated_string()) };
  93. // NOTE: BigInt and Symbol not mentioned anywhere in the WebDriver spec, as it references ES5.
  94. // It assumes that all primitives are handled above, and the value is an object for the remaining steps.
  95. if (value.is_bigint() || value.is_symbol())
  96. return ExecuteScriptResultType::JavaScriptError;
  97. // FIXME: -> a collection
  98. // FIXME: -> instance of element
  99. // FIXME: -> instance of shadow root
  100. // -> a WindowProxy object
  101. if (is<HTML::WindowProxy>(value.as_object())) {
  102. auto const& window_proxy = static_cast<HTML::WindowProxy&>(value.as_object());
  103. // If the associated browsing context of the WindowProxy object in value has been discarded, return error with
  104. // error code stale element reference.
  105. if (window_proxy.associated_browsing_context()->has_been_discarded())
  106. return ExecuteScriptResultType::BrowsingContextDiscarded;
  107. // Otherwise return success with data set to WindowProxy reference object for value.
  108. return window_proxy_reference_object(window_proxy);
  109. }
  110. // -> has an own property named "toJSON" that is a Function
  111. auto to_json = value.as_object().get_without_side_effects(vm.names.toJSON);
  112. if (to_json.is_function()) {
  113. // Return success with the value returned by Function.[[Call]](toJSON) with value as the this value.
  114. auto to_json_result = TRY_OR_JS_ERROR(to_json.as_function().internal_call(value, JS::MarkedVector<JS::Value> { vm.heap() }));
  115. if (!to_json_result.is_string())
  116. return ExecuteScriptResultType::JavaScriptError;
  117. return TRY_OR_JS_ERROR(to_json_result.as_string().deprecated_string());
  118. }
  119. // -> Otherwise
  120. // 1. If value is in seen, return error with error code javascript error.
  121. if (seen.contains(&value.as_object()))
  122. return ExecuteScriptResultType::JavaScriptError;
  123. // 2. Append value to seen.
  124. seen.set(&value.as_object());
  125. ScopeGuard remove_seen { [&] {
  126. // 4. Remove the last element of seen.
  127. seen.remove(&value.as_object());
  128. } };
  129. // 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.
  130. auto result = TRY(clone_an_object(realm, value.as_object(), seen, internal_json_clone_algorithm));
  131. // 5. Return result.
  132. return result;
  133. }
  134. // https://w3c.github.io/webdriver/#dfn-clone-an-object
  135. static ErrorOr<JsonValue, ExecuteScriptResultType> clone_an_object(JS::Realm& realm, JS::Object& value, HashTable<JS::Object*>& seen, auto const& clone_algorithm)
  136. {
  137. auto& vm = realm.vm();
  138. // 1. Let result be the value of the first matching statement, matching on value:
  139. auto get_result = [&]() -> ErrorOr<Variant<JsonArray, JsonObject>, ExecuteScriptResultType> {
  140. // -> a collection
  141. if (is_collection(value)) {
  142. // A new Array which length property is equal to the result of getting the property length of value.
  143. auto length_property = TRY_OR_JS_ERROR(value.internal_get_own_property(vm.names.length));
  144. if (!length_property->value.has_value())
  145. return ExecuteScriptResultType::JavaScriptError;
  146. auto length = TRY_OR_JS_ERROR(length_property->value->to_length(vm));
  147. if (length > NumericLimits<u32>::max())
  148. return ExecuteScriptResultType::JavaScriptError;
  149. auto array = JsonArray {};
  150. for (size_t i = 0; i < length; ++i)
  151. array.must_append(JsonValue {});
  152. return array;
  153. }
  154. // -> Otherwise
  155. else {
  156. // A new Object.
  157. return JsonObject {};
  158. }
  159. };
  160. auto result = TRY(get_result());
  161. // 2. For each enumerable own property in value, run the following substeps:
  162. for (auto& key : MUST(value.Object::internal_own_property_keys())) {
  163. // 1. Let name be the name of the property.
  164. auto name = MUST(JS::PropertyKey::from_value(vm, key));
  165. if (!value.storage_get(name)->attributes.is_enumerable())
  166. continue;
  167. // 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.
  168. auto source_property_value = TRY_OR_JS_ERROR(value.internal_get_own_property(name));
  169. if (!source_property_value.has_value() || !source_property_value->value.has_value())
  170. continue;
  171. // 3. Let cloned property result be the result of calling the clone algorithm with arguments source property value and seen.
  172. auto cloned_property_result = clone_algorithm(realm, *source_property_value->value, seen);
  173. // 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.
  174. if (!cloned_property_result.is_error()) {
  175. result.visit(
  176. [&](JsonArray& array) {
  177. // NOTE: If this was a JS array, only indexed properties would be serialized anyway.
  178. if (name.is_number())
  179. array.set(name.as_number(), cloned_property_result.value());
  180. },
  181. [&](JsonObject& object) {
  182. object.set(name.to_string(), cloned_property_result.value());
  183. });
  184. }
  185. // 5. Otherwise, return cloned property result.
  186. else {
  187. return cloned_property_result;
  188. }
  189. }
  190. return result.visit([&](auto const& value) -> JsonValue { return value; });
  191. }
  192. // https://w3c.github.io/webdriver/#dfn-execute-a-function-body
  193. static JS::ThrowCompletionOr<JS::Value> execute_a_function_body(Web::Page& page, DeprecatedString const& body, JS::MarkedVector<JS::Value> parameters)
  194. {
  195. // 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.
  196. // 1. Let window be the associated window of the current browsing context’s active document.
  197. // FIXME: This will need adjusting when WebDriver supports frames.
  198. auto& window = page.top_level_browsing_context().active_document()->window();
  199. // 2. Let environment settings be the environment settings object for window.
  200. auto& environment_settings = Web::HTML::relevant_settings_object(window);
  201. // 3. Let global scope be environment settings realm’s global environment.
  202. auto& global_scope = environment_settings.realm().global_environment();
  203. auto& realm = window.realm();
  204. bool contains_direct_call_to_eval = false;
  205. auto source_text = DeprecatedString::formatted("function() {{ {} }}", body);
  206. auto parser = JS::Parser { JS::Lexer { source_text } };
  207. auto function_expression = parser.parse_function_node<JS::FunctionExpression>();
  208. // 4. If body is not parsable as a FunctionBody or if parsing detects an early error, return Completion { [[Type]]: normal, [[Value]]: null, [[Target]]: empty }.
  209. if (parser.has_errors())
  210. return JS::js_null();
  211. // 5. If body begins with a directive prologue that contains a use strict directive then let strict be true, otherwise let strict be false.
  212. // NOTE: Handled in step 8 below.
  213. // 6. Prepare to run a script with environment settings.
  214. environment_settings.prepare_to_run_script();
  215. // 7. Prepare to run a callback with environment settings.
  216. environment_settings.prepare_to_run_callback();
  217. // 8. Let function be the result of calling FunctionCreate, with arguments:
  218. // kind
  219. // Normal.
  220. // list
  221. // An empty List.
  222. // body
  223. // The result of parsing body above.
  224. // global scope
  225. // The result of parsing global scope above.
  226. // strict
  227. // The result of parsing strict above.
  228. auto function = JS::ECMAScriptFunctionObject::create(realm, "", move(source_text), function_expression->body(), function_expression->parameters(), function_expression->function_length(), &global_scope, nullptr, function_expression->kind(), function_expression->is_strict_mode(), function_expression->might_need_arguments_object(), contains_direct_call_to_eval);
  229. // 9. Let completion be Function.[[Call]](window, parameters) with function as the this value.
  230. // NOTE: This is not entirely clear, but I don't think they mean actually passing `function` as
  231. // the this value argument, but using it as the object [[Call]] is executed on.
  232. auto completion = function->internal_call(&window, move(parameters));
  233. // 10. Clean up after running a callback with environment settings.
  234. environment_settings.clean_up_after_running_callback();
  235. // 11. Clean up after running a script with environment settings.
  236. environment_settings.clean_up_after_running_script();
  237. // 12. Return completion.
  238. return completion;
  239. }
  240. ExecuteScriptResultSerialized execute_script(Web::Page& page, DeprecatedString const& body, JS::MarkedVector<JS::Value> arguments, Optional<u64> const& timeout)
  241. {
  242. // FIXME: Use timeout.
  243. (void)timeout;
  244. auto* window = page.top_level_browsing_context().active_window();
  245. auto& realm = window->realm();
  246. // 4. Let promise be a new Promise.
  247. // NOTE: For now we skip this and handle a throw completion manually instead of using 'promise-calling'.
  248. // FIXME: 5. Run the following substeps in parallel:
  249. auto result = [&] {
  250. // 1. Let scriptPromise be the result of promise-calling execute a function body, with arguments body and arguments.
  251. auto completion = execute_a_function_body(page, body, move(arguments));
  252. // 2. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
  253. // 3. Upon rejection of scriptPromise with value r, reject promise with value r.
  254. auto result_type = completion.is_error()
  255. ? ExecuteScriptResultType::PromiseRejected
  256. : ExecuteScriptResultType::PromiseResolved;
  257. auto result_value = completion.is_error()
  258. ? *completion.throw_completion().value()
  259. : completion.value();
  260. return ExecuteScriptResult { result_type, result_value };
  261. }();
  262. // FIXME: 6. If promise is still pending and the session script timeout is reached, return error with error code script timeout.
  263. // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
  264. // 8. Upon rejection of promise with reason r, let result be a JSON clone of r, and return error with error code javascript error and data result.
  265. auto json_value_or_error = json_clone(realm, result.value);
  266. if (json_value_or_error.is_error()) {
  267. auto error_object = JsonObject {};
  268. error_object.set("name", "Error");
  269. error_object.set("message", "Could not clone result value");
  270. return { ExecuteScriptResultType::JavaScriptError, move(error_object) };
  271. }
  272. return { result.type, json_value_or_error.release_value() };
  273. }
  274. ExecuteScriptResultSerialized execute_async_script(Web::Page& page, DeprecatedString const& body, JS::MarkedVector<JS::Value> arguments, Optional<u64> const& timeout)
  275. {
  276. auto* document = page.top_level_browsing_context().active_document();
  277. auto& settings_object = document->relevant_settings_object();
  278. auto* window = page.top_level_browsing_context().active_window();
  279. auto& realm = window->realm();
  280. auto& vm = window->vm();
  281. auto start = Time::now_monotonic();
  282. // 4. Let promise be a new Promise.
  283. auto promise = JS::Promise::create(realm);
  284. // FIXME: 5 Run the following substeps in parallel:
  285. auto result = [&] {
  286. // NOTE: We need to push an execution context in order to make create_resolving_functions() succeed.
  287. vm.push_execution_context(settings_object.realm_execution_context());
  288. // 1. Let resolvingFunctions be CreateResolvingFunctions(promise).
  289. auto resolving_functions = promise->create_resolving_functions();
  290. VERIFY(&settings_object.realm_execution_context() == &vm.running_execution_context());
  291. vm.pop_execution_context();
  292. // 2. Append resolvingFunctions.[[Resolve]] to arguments.
  293. arguments.append(resolving_functions.resolve);
  294. // 3. Let result be the result of calling execute a function body, with arguments body and arguments.
  295. // FIXME: 'result' -> 'scriptResult' (spec issue)
  296. auto script_result = execute_a_function_body(page, body, move(arguments));
  297. // 4.If scriptResult.[[Type]] is not normal, then reject promise with value scriptResult.[[Value]], and abort these steps.
  298. // NOTE: Prior revisions of this specification did not recognize the return value of the provided script.
  299. // In order to preserve legacy behavior, the return value only influences the command if it is a
  300. // "thenable" object or if determining this produces an exception.
  301. if (script_result.is_throw_completion())
  302. return ExecuteScriptResult { ExecuteScriptResultType::PromiseRejected, *script_result.throw_completion().value() };
  303. // 5. If Type(scriptResult.[[Value]]) is not Object, then abort these steps.
  304. if (!script_result.value().is_object())
  305. return ExecuteScriptResult { ExecuteScriptResultType::PromiseResolved, JS::js_null() };
  306. // 6. Let then be Get(scriptResult.[[Value]], "then").
  307. auto then = script_result.value().as_object().get(vm.names.then);
  308. // 7. If then.[[Type]] is not normal, then reject promise with value then.[[Value]], and abort these steps.
  309. if (then.is_throw_completion())
  310. return ExecuteScriptResult { ExecuteScriptResultType::PromiseRejected, *then.throw_completion().value() };
  311. // 8. If IsCallable(then.[[Type]]) is false, then abort these steps.
  312. if (!then.value().is_function())
  313. return ExecuteScriptResult { ExecuteScriptResultType::PromiseResolved, JS::js_null() };
  314. // 9. Let scriptPromise be PromiseResolve(Promise, scriptResult.[[Value]]).
  315. auto script_promise_or_error = JS::promise_resolve(vm, realm.intrinsics().promise_constructor(), script_result.value());
  316. if (script_promise_or_error.is_throw_completion())
  317. return ExecuteScriptResult { ExecuteScriptResultType::PromiseRejected, *script_promise_or_error.throw_completion().value() };
  318. auto& script_promise = static_cast<JS::Promise&>(*script_promise_or_error.value());
  319. vm.custom_data()->spin_event_loop_until([&] {
  320. if (script_promise.state() != JS::Promise::State::Pending)
  321. return true;
  322. if (timeout.has_value() && (Time::now_monotonic() - start) > Time::from_seconds(static_cast<i64>(*timeout)))
  323. return true;
  324. return false;
  325. });
  326. // 10. Upon fulfillment of scriptPromise with value v, resolve promise with value v.
  327. if (script_promise.state() == JS::Promise::State::Fulfilled)
  328. return ExecuteScriptResult { ExecuteScriptResultType::PromiseResolved, script_promise.result() };
  329. // 11. Upon rejection of scriptPromise with value r, reject promise with value r.
  330. if (script_promise.state() == JS::Promise::State::Rejected)
  331. return ExecuteScriptResult { ExecuteScriptResultType::PromiseRejected, script_promise.result() };
  332. return ExecuteScriptResult { ExecuteScriptResultType::Timeout, script_promise.result() };
  333. }();
  334. // 6. If promise is still pending and session script timeout milliseconds is reached, return error with error code script timeout.
  335. // 7. Upon fulfillment of promise with value v, let result be a JSON clone of v, and return success with data result.
  336. // 8. Upon rejection of promise with reason r, let result be a JSON clone of r, and return error with error code javascript error and data result.
  337. auto json_value_or_error = json_clone(realm, result.value);
  338. if (json_value_or_error.is_error()) {
  339. auto error_object = JsonObject {};
  340. error_object.set("name", "Error");
  341. error_object.set("message", "Could not clone result value");
  342. return { ExecuteScriptResultType::JavaScriptError, move(error_object) };
  343. }
  344. return { result.type, json_value_or_error.release_value() };
  345. }
  346. }