Capabilities.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /*
  2. * Copyright (c) 2022, Tim Flynn <trflynn89@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/Optional.h>
  10. #include <LibWeb/WebDriver/Capabilities.h>
  11. #include <LibWeb/WebDriver/TimeoutsConfiguration.h>
  12. namespace Web::WebDriver {
  13. // https://w3c.github.io/webdriver/#dfn-deserialize-as-a-page-load-strategy
  14. static Response deserialize_as_a_page_load_strategy(JsonValue value)
  15. {
  16. // 1. If value is not a string return an error with error code invalid argument.
  17. if (!value.is_string())
  18. return Error::from_code(ErrorCode::InvalidArgument, "Capability pageLoadStrategy must be a string"sv);
  19. // 2. If there is no entry in the table of page load strategies with keyword value return an error with error code invalid argument.
  20. if (!value.as_string().is_one_of("none"sv, "eager"sv, "normal"sv))
  21. return Error::from_code(ErrorCode::InvalidArgument, "Invalid pageLoadStrategy capability"sv);
  22. // 3. Return success with data value.
  23. return value;
  24. }
  25. // https://w3c.github.io/webdriver/#dfn-deserialize-as-an-unhandled-prompt-behavior
  26. static Response deserialize_as_an_unhandled_prompt_behavior(JsonValue value)
  27. {
  28. // 1. If value is not a string return an error with error code invalid argument.
  29. if (!value.is_string())
  30. return Error::from_code(ErrorCode::InvalidArgument, "Capability unhandledPromptBehavior must be a string"sv);
  31. // 2. If value is not present as a keyword in the known prompt handling approaches table return an error with error code invalid argument.
  32. if (!value.as_string().is_one_of("dismiss"sv, "accept"sv, "dismiss and notify"sv, "accept and notify"sv, "ignore"sv))
  33. return Error::from_code(ErrorCode::InvalidArgument, "Invalid pageLoadStrategy capability"sv);
  34. // 3. Return success with data value.
  35. return value;
  36. }
  37. // https://w3c.github.io/webdriver/#dfn-validate-capabilities
  38. static ErrorOr<JsonObject, Error> validate_capabilities(JsonValue const& capability)
  39. {
  40. // 1. If capability is not a JSON Object return an error with error code invalid argument.
  41. if (!capability.is_object())
  42. return Error::from_code(ErrorCode::InvalidArgument, "Capability is not an Object"sv);
  43. // 2. Let result be an empty JSON Object.
  44. JsonObject result;
  45. // 3. For each enumerable own property in capability, run the following substeps:
  46. TRY(capability.as_object().try_for_each_member([&](auto const& name, auto const& value) -> ErrorOr<void, Error> {
  47. // a. Let name be the name of the property.
  48. // b. Let value be the result of getting a property named name from capability.
  49. // c. Run the substeps of the first matching condition:
  50. JsonValue deserialized;
  51. // -> value is null
  52. if (value.is_null()) {
  53. // Let deserialized be set to null.
  54. }
  55. // -> name equals "acceptInsecureCerts"
  56. else if (name == "acceptInsecureCerts"sv) {
  57. // If value is not a boolean return an error with error code invalid argument. Otherwise, let deserialized be set to value
  58. if (!value.is_bool())
  59. return Error::from_code(ErrorCode::InvalidArgument, "Capability acceptInsecureCerts must be a boolean"sv);
  60. deserialized = value;
  61. }
  62. // -> name equals "browserName"
  63. // -> name equals "browserVersion"
  64. // -> name equals "platformName"
  65. else if (name.is_one_of("browserName"sv, "browser_version"sv, "platformName"sv)) {
  66. // If value is not a string return an error with error code invalid argument. Otherwise, let deserialized be set to value.
  67. if (!value.is_string())
  68. return Error::from_code(ErrorCode::InvalidArgument, String::formatted("Capability {} must be a string", name));
  69. deserialized = value;
  70. }
  71. // -> name equals "pageLoadStrategy"
  72. else if (name == "pageLoadStrategy"sv) {
  73. // Let deserialized be the result of trying to deserialize as a page load strategy with argument value.
  74. deserialized = TRY(deserialize_as_a_page_load_strategy(value));
  75. }
  76. // FIXME: -> name equals "proxy"
  77. // FIXME: Let deserialized be the result of trying to deserialize as a proxy with argument value.
  78. // -> name equals "strictFileInteractability"
  79. else if (name == "strictFileInteractability"sv) {
  80. // If value is not a boolean return an error with error code invalid argument. Otherwise, let deserialized be set to value
  81. if (!value.is_bool())
  82. return Error::from_code(ErrorCode::InvalidArgument, "Capability strictFileInteractability must be a boolean"sv);
  83. deserialized = value;
  84. }
  85. // -> name equals "timeouts"
  86. else if (name == "timeouts"sv) {
  87. // Let deserialized be the result of trying to JSON deserialize as a timeouts configuration the value.
  88. auto timeouts = TRY(json_deserialize_as_a_timeouts_configuration(value));
  89. deserialized = JsonValue { timeouts_object(timeouts) };
  90. }
  91. // -> name equals "unhandledPromptBehavior"
  92. else if (name == "unhandledPromptBehavior"sv) {
  93. // Let deserialized be the result of trying to deserialize as an unhandled prompt behavior with argument value.
  94. deserialized = TRY(deserialize_as_an_unhandled_prompt_behavior(value));
  95. }
  96. // FIXME: -> name is the name of an additional WebDriver capability
  97. // FIXME: Let deserialized be the result of trying to run the additional capability deserialization algorithm for the extension capability corresponding to name, with argument value.
  98. // FIXME: -> name is the key of an extension capability
  99. // FIXME: If name is known to the implementation, let deserialized be the result of trying to deserialize value in an implementation-specific way. Otherwise, let deserialized be set to value.
  100. // -> The remote end is an endpoint node
  101. else {
  102. // Return an error with error code invalid argument.
  103. return Error::from_code(ErrorCode::InvalidArgument, String::formatted("Unrecognized capability: {}", name));
  104. }
  105. // d. If deserialized is not null, set a property on result with name name and value deserialized.
  106. if (!deserialized.is_null())
  107. result.set(name, move(deserialized));
  108. return {};
  109. }));
  110. // 4. Return success with data result.
  111. return result;
  112. }
  113. // https://w3c.github.io/webdriver/#dfn-merging-capabilities
  114. static ErrorOr<JsonObject, Error> merge_capabilities(JsonObject const& primary, Optional<JsonObject const&> const& secondary)
  115. {
  116. // 1. Let result be a new JSON Object.
  117. JsonObject result;
  118. // 2. For each enumerable own property in primary, run the following substeps:
  119. primary.for_each_member([&](auto const& name, auto const& value) {
  120. // a. Let name be the name of the property.
  121. // b. Let value be the result of getting a property named name from primary.
  122. // c. Set a property on result with name name and value value.
  123. result.set(name, value);
  124. });
  125. // 3. If secondary is undefined, return result.
  126. if (!secondary.has_value())
  127. return result;
  128. // 4. For each enumerable own property in secondary, run the following substeps:
  129. TRY(secondary->try_for_each_member([&](auto const& name, auto const& value) -> ErrorOr<void, Error> {
  130. // a. Let name be the name of the property.
  131. // b. Let value be the result of getting a property named name from secondary.
  132. // c. Let primary value be the result of getting the property name from primary.
  133. auto const* primary_value = primary.get_ptr(name);
  134. // d. If primary value is not undefined, return an error with error code invalid argument.
  135. if (primary_value != nullptr)
  136. return Error::from_code(ErrorCode::InvalidArgument, String::formatted("Unable to merge capability {}", name));
  137. // e. Set a property on result with name name and value value.
  138. result.set(name, value);
  139. return {};
  140. }));
  141. // 5. Return result.
  142. return result;
  143. }
  144. // https://w3c.github.io/webdriver/#dfn-capabilities-processing
  145. Response process_capabilities(JsonValue const& parameters)
  146. {
  147. if (!parameters.is_object())
  148. return Error::from_code(ErrorCode::InvalidArgument, "Session parameters is not an object"sv);
  149. // 1. Let capabilities request be the result of getting the property "capabilities" from parameters.
  150. // a. If capabilities request is not a JSON Object, return error with error code invalid argument.
  151. auto const* capabilities_request_ptr = parameters.as_object().get_ptr("capabilities"sv);
  152. if (!capabilities_request_ptr || !capabilities_request_ptr->is_object())
  153. return Error::from_code(ErrorCode::InvalidArgument, "Capabilities is not an object"sv);
  154. auto const& capabilities_request = capabilities_request_ptr->as_object();
  155. // 2. Let required capabilities be the result of getting the property "alwaysMatch" from capabilities request.
  156. // a. If required capabilities is undefined, set the value to an empty JSON Object.
  157. JsonObject required_capabilities;
  158. if (auto const* capability = capabilities_request.get_ptr("alwaysMatch"sv)) {
  159. // b. Let required capabilities be the result of trying to validate capabilities with argument required capabilities.
  160. required_capabilities = TRY(validate_capabilities(*capability));
  161. }
  162. // 3. Let all first match capabilities be the result of getting the property "firstMatch" from capabilities request.
  163. JsonArray all_first_match_capabilities;
  164. if (auto const* capabilities = capabilities_request.get_ptr("firstMatch"sv)) {
  165. // b. If all first match capabilities is not a JSON List with one or more entries, return error with error code invalid argument.
  166. if (!capabilities->is_array() || capabilities->as_array().is_empty())
  167. return Error::from_code(ErrorCode::InvalidArgument, "Capability firstMatch must be an array with at least one entry"sv);
  168. all_first_match_capabilities = capabilities->as_array();
  169. } else {
  170. // a. If all first match capabilities is undefined, set the value to a JSON List with a single entry of an empty JSON Object.
  171. all_first_match_capabilities.append(JsonObject {});
  172. }
  173. // 4. Let validated first match capabilities be an empty JSON List.
  174. JsonArray validated_first_match_capabilities;
  175. validated_first_match_capabilities.ensure_capacity(all_first_match_capabilities.size());
  176. // 5. For each first match capabilities corresponding to an indexed property in all first match capabilities:
  177. TRY(all_first_match_capabilities.try_for_each([&](auto const& first_match_capabilities) -> ErrorOr<void, Error> {
  178. // a. Let validated capabilities be the result of trying to validate capabilities with argument first match capabilities.
  179. auto validated_capabilities = TRY(validate_capabilities(first_match_capabilities));
  180. // b. Append validated capabilities to validated first match capabilities.
  181. validated_first_match_capabilities.append(move(validated_capabilities));
  182. return {};
  183. }));
  184. // 6. Let merged capabilities be an empty List.
  185. JsonArray merged_capabilities;
  186. merged_capabilities.ensure_capacity(validated_first_match_capabilities.size());
  187. // 7. For each first match capabilities corresponding to an indexed property in validated first match capabilities:
  188. TRY(validated_first_match_capabilities.try_for_each([&](auto const& first_match_capabilities) -> ErrorOr<void, Error> {
  189. // a. Let merged be the result of trying to merge capabilities with required capabilities and first match capabilities as arguments.
  190. auto merged = TRY(merge_capabilities(required_capabilities, first_match_capabilities.as_object()));
  191. // b. Append merged to merged capabilities.
  192. merged_capabilities.append(move(merged));
  193. return {};
  194. }));
  195. // FIXME: 8. For each capabilities corresponding to an indexed property in merged capabilities:
  196. // FIXME: a. Let matched capabilities be the result of trying to match capabilities with capabilities as an argument.
  197. // FIXME: b. If matched capabilities is not null, return success with data matched capabilities.
  198. // For now, we just assume the first validated capabilties object is a match.
  199. return merged_capabilities.take(0);
  200. // 9. Return success with data null.
  201. }
  202. }