JSON.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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 <AK/HashTable.h>
  8. #include <AK/JsonArray.h>
  9. #include <AK/JsonObject.h>
  10. #include <AK/JsonValue.h>
  11. #include <AK/NumericLimits.h>
  12. #include <AK/Variant.h>
  13. #include <LibJS/Runtime/Array.h>
  14. #include <LibJS/Runtime/JSONObject.h>
  15. #include <LibWeb/DOM/DOMTokenList.h>
  16. #include <LibWeb/DOM/Document.h>
  17. #include <LibWeb/DOM/HTMLCollection.h>
  18. #include <LibWeb/DOM/NodeList.h>
  19. #include <LibWeb/DOM/ShadowRoot.h>
  20. #include <LibWeb/FileAPI/FileList.h>
  21. #include <LibWeb/HTML/BrowsingContext.h>
  22. #include <LibWeb/HTML/HTMLAllCollection.h>
  23. #include <LibWeb/HTML/HTMLFormControlsCollection.h>
  24. #include <LibWeb/HTML/HTMLOptionsCollection.h>
  25. #include <LibWeb/HTML/WindowProxy.h>
  26. #include <LibWeb/WebDriver/Contexts.h>
  27. #include <LibWeb/WebDriver/ElementReference.h>
  28. #include <LibWeb/WebDriver/JSON.h>
  29. namespace Web::WebDriver {
  30. #define TRY_OR_JS_ERROR(expression) \
  31. ({ \
  32. auto&& _temporary_result = (expression); \
  33. if (_temporary_result.is_error()) [[unlikely]] \
  34. return WebDriver::Error::from_code(ErrorCode::JavascriptError, "Script returned an error"); \
  35. static_assert(!::AK::Detail::IsLvalueReference<decltype(_temporary_result.release_value())>, \
  36. "Do not return a reference from a fallible expression"); \
  37. _temporary_result.release_value(); \
  38. })
  39. using SeenMap = HashTable<GC::RawPtr<JS::Object const>>;
  40. // https://w3c.github.io/webdriver/#dfn-collection
  41. static bool is_collection(JS::Object const& value)
  42. {
  43. // A collection is an Object that implements the Iterable interface, and whose:
  44. return (
  45. // - initial value of the toString own property is "Arguments"
  46. value.has_parameter_map()
  47. // - instance of Array
  48. || is<JS::Array>(value)
  49. // - instance of DOMTokenList
  50. || is<DOM::DOMTokenList>(value)
  51. // - instance of FileList
  52. || is<FileAPI::FileList>(value)
  53. // - instance of HTMLAllCollection
  54. || is<HTML::HTMLAllCollection>(value)
  55. // - instance of HTMLCollection
  56. || is<DOM::HTMLCollection>(value)
  57. // - instance of HTMLFormControlsCollection
  58. || is<HTML::HTMLFormControlsCollection>(value)
  59. // - instance of HTMLOptionsCollection
  60. || is<HTML::HTMLOptionsCollection>(value)
  61. // - instance of NodeList
  62. || is<DOM::NodeList>(value));
  63. }
  64. // https://w3c.github.io/webdriver/#dfn-clone-an-object
  65. template<typename ResultType, typename CloneAlgorithm>
  66. static ErrorOr<ResultType, WebDriver::Error> clone_an_object(HTML::BrowsingContext const& browsing_context, JS::Object const& value, SeenMap& seen, CloneAlgorithm const& clone_algorithm)
  67. {
  68. static constexpr bool is_json_value = IsSame<ResultType, JsonValue>;
  69. auto& realm = browsing_context.active_document()->realm();
  70. auto& vm = realm.vm();
  71. // 1. If value is in seen, return error with error code javascript error.
  72. if (seen.contains(value))
  73. return WebDriver::Error::from_code(ErrorCode::JavascriptError, "Attempted to recursively clone an Object"sv);
  74. // 2. Append value to seen.
  75. seen.set(value);
  76. // 3. Let result be the value of the first matching statement, matching on value:
  77. auto result = TRY(([&]() -> ErrorOr<ResultType, WebDriver::Error> {
  78. // -> a collection
  79. if (is_collection(value)) {
  80. // A new Array which length property is equal to the result of getting the property length of value.
  81. auto length_property = TRY_OR_JS_ERROR(value.get(vm.names.length));
  82. auto length = TRY_OR_JS_ERROR(length_property.to_length(vm));
  83. if (length > NumericLimits<u32>::max())
  84. return WebDriver::Error::from_code(ErrorCode::JavascriptError, "Length of Object too large"sv);
  85. if constexpr (is_json_value)
  86. return JsonArray { length };
  87. else
  88. return TRY_OR_JS_ERROR(JS::Array::create(realm, length));
  89. }
  90. // -> Otherwise
  91. else {
  92. // A new Object.
  93. if constexpr (is_json_value)
  94. return JsonObject {};
  95. else
  96. return JS::Object::create(realm, realm.intrinsics().object_prototype());
  97. }
  98. }()));
  99. Optional<WebDriver::Error> error;
  100. // 4. For each enumerable property in value, run the following substeps:
  101. (void)value.enumerate_object_properties([&](auto property) -> Optional<JS::Completion> {
  102. // 1. Let name be the name of the property.
  103. auto name = MUST(JS::PropertyKey::from_value(vm, property));
  104. // 2. Let source property value be the result of getting a property named name from value. If doing so causes
  105. // script to be run and that script throws an error, return error with error code javascript error.
  106. auto source_property_value = value.get(name);
  107. if (source_property_value.is_error()) {
  108. error = WebDriver::Error::from_code(ErrorCode::JavascriptError, "Script returned an error");
  109. return JS::normal_completion({});
  110. }
  111. // 3. Let cloned property result be the result of calling the clone algorithm with session, source property
  112. // value and seen.
  113. auto cloned_property_result = clone_algorithm(browsing_context, source_property_value.value(), seen);
  114. // 4. If cloned property result is a success, set a property of result with name name and value equal to cloned
  115. // property result's data.
  116. if (!cloned_property_result.is_error()) {
  117. if constexpr (is_json_value) {
  118. if (result.is_array() && name.is_number())
  119. result.as_array().set(name.as_number(), cloned_property_result.value());
  120. else if (result.is_object())
  121. result.as_object().set(name.to_string(), cloned_property_result.value());
  122. } else {
  123. (void)result->set(name, cloned_property_result.value(), JS::Object::ShouldThrowExceptions::No);
  124. }
  125. }
  126. // 5. Otherwise, return cloned property result.
  127. else {
  128. error = cloned_property_result.release_error();
  129. return JS::normal_completion({});
  130. }
  131. return {};
  132. });
  133. if (error.has_value())
  134. return error.release_value();
  135. // 5. Remove the last element of seen.
  136. seen.remove(value);
  137. // 6. Return success with data result.
  138. return result;
  139. }
  140. // https://w3c.github.io/webdriver/#dfn-internal-json-clone
  141. static Response internal_json_clone(HTML::BrowsingContext const& browsing_context, JS::Value value, SeenMap& seen)
  142. {
  143. auto& vm = browsing_context.vm();
  144. // To internal JSON clone given session, value and seen, return the value of the first matching statement, matching
  145. // on value:
  146. // -> undefined
  147. // -> null
  148. if (value.is_nullish()) {
  149. // Return success with data null.
  150. return JsonValue {};
  151. }
  152. // -> type Boolean
  153. // -> type Number
  154. // -> type String
  155. // Return success with data value.
  156. if (value.is_boolean())
  157. return JsonValue { value.as_bool() };
  158. if (value.is_number())
  159. return JsonValue { value.as_double() };
  160. if (value.is_string())
  161. return JsonValue { value.as_string().byte_string() };
  162. // AD-HOC: BigInt and Symbol not mentioned anywhere in the WebDriver spec, as it references ES5.
  163. // It assumes that all primitives are handled above, and the value is an object for the remaining steps.
  164. if (value.is_bigint())
  165. return WebDriver::Error::from_code(ErrorCode::JavascriptError, "Cannot clone a BigInt"sv);
  166. if (value.is_symbol())
  167. return WebDriver::Error::from_code(ErrorCode::JavascriptError, "Cannot clone a Symbol"sv);
  168. VERIFY(value.is_object());
  169. auto const& object = static_cast<JS::Object const&>(value.as_object());
  170. // -> instance of Element
  171. if (is<DOM::Element>(object)) {
  172. auto const& element = static_cast<DOM::Element const&>(object);
  173. // If the element is stale, return error with error code stale element reference.
  174. if (is_element_stale(element)) {
  175. return WebDriver::Error::from_code(ErrorCode::StaleElementReference, "Referenced element has become stale"sv);
  176. }
  177. // Otherwise:
  178. else {
  179. // 1. Let reference be the web element reference object for session and value.
  180. auto reference = web_element_reference_object(browsing_context, element);
  181. // 2. Return success with data reference.
  182. return JsonValue { move(reference) };
  183. }
  184. }
  185. // -> instance of ShadowRoot
  186. if (is<DOM::ShadowRoot>(object)) {
  187. auto const& shadow_root = static_cast<DOM::ShadowRoot const&>(object);
  188. // If the shadow root is detached, return error with error code detached shadow root.
  189. if (is_shadow_root_detached(shadow_root)) {
  190. return WebDriver::Error::from_code(ErrorCode::DetachedShadowRoot, "Referenced shadow root has become detached"sv);
  191. }
  192. // Otherwise:
  193. else {
  194. // 1. Let reference be the shadow root reference object for session and value.
  195. auto reference = shadow_root_reference_object(browsing_context, shadow_root);
  196. // 2. Return success with data reference.
  197. return JsonValue { move(reference) };
  198. }
  199. }
  200. // -> a WindowProxy object
  201. if (is<HTML::WindowProxy>(object)) {
  202. auto const& window_proxy = static_cast<HTML::WindowProxy const&>(object);
  203. // If the associated browsing context of the WindowProxy object in value has been destroyed, return error
  204. // with error code stale element reference.
  205. if (window_proxy.associated_browsing_context()->has_navigable_been_destroyed()) {
  206. return WebDriver::Error::from_code(ErrorCode::StaleElementReference, "Browsing context has been discarded"sv);
  207. }
  208. // Otherwise:
  209. else {
  210. // 1. Let reference be the WindowProxy reference object for value.
  211. auto reference = window_proxy_reference_object(window_proxy);
  212. // 2. Return success with data reference.
  213. return JsonValue { move(reference) };
  214. }
  215. }
  216. // -> has an own property named "toJSON" that is a Function
  217. if (auto to_json = object.get_without_side_effects(vm.names.toJSON); to_json.is_function()) {
  218. // Return success with the value returned by Function.[[Call]](toJSON) with value as the this value.
  219. auto to_json_result = TRY_OR_JS_ERROR(to_json.as_function().internal_call(value, GC::MarkedVector<JS::Value> { vm.heap() }));
  220. if (!to_json_result.is_string())
  221. return WebDriver::Error::from_code(ErrorCode::JavascriptError, "toJSON did not return a String"sv);
  222. return JsonValue { to_json_result.as_string().byte_string() };
  223. }
  224. // -> Otherwise
  225. // 1. Let result be clone an object with session value and seen, and internal JSON clone as the clone algorithm.
  226. auto result = TRY(clone_an_object<JsonValue>(browsing_context, object, seen, internal_json_clone));
  227. // 2. Return success with data result.
  228. return result;
  229. }
  230. // https://w3c.github.io/webdriver/#dfn-json-clone
  231. Response json_clone(HTML::BrowsingContext const& browsing_context, JS::Value value)
  232. {
  233. SeenMap seen;
  234. // To JSON clone given session and value, return the result of internal JSON clone with session, value and an empty List.
  235. return internal_json_clone(browsing_context, value, seen);
  236. }
  237. // https://w3c.github.io/webdriver/#dfn-json-deserialize
  238. static ErrorOr<JS::Value, WebDriver::Error> internal_json_deserialize(HTML::BrowsingContext const& browsing_context, JS::Value value, SeenMap& seen)
  239. {
  240. // 1. If seen is not provided, let seen be an empty List.
  241. // 2. Jump to the first appropriate step below:
  242. // 3. Matching on value:
  243. // -> undefined
  244. // -> null
  245. // -> type Boolean
  246. // -> type Number
  247. // -> type String
  248. if (value.is_nullish() || value.is_boolean() || value.is_number() || value.is_string()) {
  249. // Return success with data value.
  250. return value;
  251. }
  252. // -> Object that represents a web element
  253. if (represents_a_web_element(value)) {
  254. // Return the deserialized web element of value.
  255. return deserialize_web_element(browsing_context, value.as_object());
  256. }
  257. // -> Object that represents a shadow root
  258. if (represents_a_shadow_root(value)) {
  259. // Return the deserialized shadow root of value.
  260. return deserialize_shadow_root(browsing_context, value.as_object());
  261. }
  262. // -> Object that represents a web frame
  263. if (represents_a_web_frame(value)) {
  264. // Return the deserialized web frame of value.
  265. return deserialize_web_frame(value.as_object());
  266. }
  267. // -> Object that represents a web window
  268. if (represents_a_web_window(value)) {
  269. // Return the deserialized web window of value.
  270. return deserialize_web_window(value.as_object());
  271. }
  272. // -> instance of Array
  273. // -> instance of Object
  274. if (value.is_object()) {
  275. // Return clone an object algorithm with session, value and seen, and the JSON deserialize algorithm as the
  276. // clone algorithm.
  277. return clone_an_object<GC::Ref<JS::Object>>(browsing_context, value.as_object(), seen, internal_json_deserialize);
  278. }
  279. return WebDriver::Error::from_code(ErrorCode::JavascriptError, "Unrecognized value type"sv);
  280. }
  281. // https://w3c.github.io/webdriver/#dfn-json-deserialize
  282. ErrorOr<JS::Value, WebDriver::Error> json_deserialize(HTML::BrowsingContext const& browsing_context, JsonValue const& value)
  283. {
  284. auto& vm = browsing_context.vm();
  285. SeenMap seen;
  286. return internal_json_deserialize(browsing_context, JS::JSONObject::parse_json_value(vm, value), seen);
  287. }
  288. }