diff --git a/dotnet/src/webdriver/Command.cs b/dotnet/src/webdriver/Command.cs index ddf6ed4228995..c7b863e8bff1c 100644 --- a/dotnet/src/webdriver/Command.cs +++ b/dotnet/src/webdriver/Command.cs @@ -20,6 +20,7 @@ using OpenQA.Selenium.Internal; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Serialization.Metadata; @@ -31,15 +32,29 @@ namespace OpenQA.Selenium /// public class Command { - private readonly static JsonSerializerOptions s_jsonSerializerOptions = new() + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = $"All trimming-unsafe access points to {nameof(s_jsonSerializerOptions)} are annotated as such")] + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = $"All AOT-unsafe access points to {nameof(s_jsonSerializerOptions)} are annotated as such")] + private static class JsonOptionsHolder { - TypeInfoResolverChain = + public readonly static JsonSerializerOptions s_jsonSerializerOptions = new() { - CommandJsonSerializerContext.Default, - new DefaultJsonTypeInfoResolver() - }, - Converters = { new ResponseValueJsonConverter() } - }; + TypeInfoResolver = GetTypeInfoResolver(), + Converters = { new ResponseValueJsonConverter() } + }; + + private static IJsonTypeInfoResolver GetTypeInfoResolver() + { +#if NET8_0_OR_GREATER + if (!System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported) + { + return CommandJsonSerializerContext.Default; + } +#endif + return JsonTypeInfoResolver.Combine(CommandJsonSerializerContext.Default, new DefaultJsonTypeInfoResolver()); + } + } + + private readonly Dictionary _parameters; /// /// Initializes a new instance of the class using a command name and a JSON-encoded string for the parameters. @@ -47,8 +62,10 @@ public class Command /// Name of the command /// Parameters for the command as a JSON-encoded string. public Command(string name, string jsonParameters) - : this(null, name, ConvertParametersFromJson(jsonParameters)) { + this.SessionId = null; + this._parameters = ConvertParametersFromJson(jsonParameters) ?? new Dictionary(); + this.Name = name ?? throw new ArgumentNullException(nameof(name)); } /// @@ -61,7 +78,7 @@ public Command(string name, string jsonParameters) public Command(SessionId? sessionId, string name, Dictionary? parameters) { this.SessionId = sessionId; - this.Parameters = parameters ?? new Dictionary(); + this._parameters = parameters ?? new Dictionary(); this.Name = name ?? throw new ArgumentNullException(nameof(name)); } @@ -81,18 +98,32 @@ public Command(SessionId? sessionId, string name, Dictionary? p /// Gets the parameters of the command /// [JsonPropertyName("parameters")] - public Dictionary Parameters { get; } + public Dictionary Parameters + { + [RequiresUnreferencedCode("Adding untyped parameter values for JSON serialization has best-effort AOT support. Ensure only Selenium types and well-known .NET types are added.")] + [RequiresDynamicCode("Adding untyped parameter values for JSON serialization has best-effort AOT support. Ensure only Selenium types and well-known .NET types are added.")] + get => _parameters; + } /// /// Gets the parameters of the command as a JSON-encoded string. /// public string ParametersAsJsonString { + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = $"All trimming-unsafe access points to {nameof(_parameters)} are annotated as such")] + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = $"All AOT-unsafe access points to {nameof(_parameters)} are annotated as such")] get { - if (this.Parameters != null && this.Parameters.Count > 0) + if (HasParameters()) { - return JsonSerializer.Serialize(this.Parameters, s_jsonSerializerOptions); + try + { + return JsonSerializer.Serialize(this._parameters, JsonOptionsHolder.s_jsonSerializerOptions); + } + catch (NotSupportedException ex) + { + throw new WebDriverException("Attempted to serialize an unsupported type. Ensure you are using Selenium types, or well-known .NET types such as Dictionary and object[]", ex); + } } else { @@ -101,6 +132,25 @@ public string ParametersAsJsonString } } + internal bool HasParameters() + { + return this._parameters != null && this._parameters.Count > 0; + } + + internal bool TryGetValueAndRemoveIfNotNull(string key, [NotNullWhen(true)] out object? value) + { + if (this._parameters.TryGetValue(key, out value)) + { + if (value is not null) + { + this._parameters.Remove(key); + return true; + } + } + + return false; + } + /// /// Returns a string of the Command object /// @@ -168,6 +218,7 @@ public override string ToString() [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(Dictionary))] [JsonSerializable(typeof(Dictionary))] + [JsonSerializable(typeof(object[]))] [JsonSourceGenerationOptions(Converters = [typeof(ResponseValueJsonConverter)])] internal partial class CommandJsonSerializerContext : JsonSerializerContext; } diff --git a/dotnet/src/webdriver/HttpCommandInfo.cs b/dotnet/src/webdriver/HttpCommandInfo.cs index 56c8beef152a8..785f81eb9f137 100644 --- a/dotnet/src/webdriver/HttpCommandInfo.cs +++ b/dotnet/src/webdriver/HttpCommandInfo.cs @@ -119,17 +119,13 @@ private static string GetCommandPropertyValue(string propertyName, Command comma propertyValue = commandToExecute.SessionId.ToString(); } } - else if (commandToExecute.Parameters != null && commandToExecute.Parameters.Count > 0) + else if (commandToExecute.HasParameters()) { // Extract the URL parameter, and remove it from the parameters dictionary // so it doesn't get transmitted as a JSON parameter. - if (commandToExecute.Parameters.TryGetValue(propertyName, out var propertyValueObject)) + if (commandToExecute.TryGetValueAndRemoveIfNotNull(propertyName, out var propertyValueObject)) { - if (propertyValueObject != null) - { - propertyValue = propertyValueObject.ToString()!; - commandToExecute.Parameters.Remove(propertyName); - } + propertyValue = propertyValueObject.ToString()!; } } diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs index 4008ec1e15b66..e1ddbc36aa025 100644 --- a/dotnet/src/webdriver/WebDriver.cs +++ b/dotnet/src/webdriver/WebDriver.cs @@ -40,7 +40,7 @@ public class WebDriver : IWebDriver, ISearchContext, IJavaScriptExecutor, IFinds /// protected static readonly TimeSpan DefaultCommandTimeout = TimeSpan.FromSeconds(60); private IFileDetector fileDetector = new DefaultFileDetector(); - private NetworkManager network; + private NetworkManager? network; private WebElementFactory elementFactory; private readonly List registeredCommands = new List();