Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dotnet] Make classic WebDriver commands/responses AOT compatible #14574

Merged
merged 29 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1e8b1dc
Win?
nvborisenko Oct 7, 2024
b927052
int64?
nvborisenko Oct 7, 2024
a076702
Revert "int64?"
nvborisenko Oct 7, 2024
8e9a755
Long in command
nvborisenko Oct 7, 2024
85c3989
Boolean!
nvborisenko Oct 7, 2024
5050151
char[]
nvborisenko Oct 7, 2024
61e45af
Double
nvborisenko Oct 7, 2024
0416816
Remove extra
nvborisenko Oct 7, 2024
8509830
uint
nvborisenko Oct 7, 2024
1d781a8
float
nvborisenko Oct 7, 2024
419e833
Update Command.cs
nvborisenko Oct 8, 2024
01b62a6
Add uint
nvborisenko Oct 8, 2024
1b359e2
Make serialization generic
nvborisenko Oct 8, 2024
51ae258
Interesting
nvborisenko Oct 8, 2024
38ad419
Remove extra command
nvborisenko Oct 8, 2024
658d1d5
Remove extra response
nvborisenko Oct 8, 2024
f26429b
Add Proxy type as serializable
nvborisenko Oct 18, 2024
05d8829
Merge remote-tracking branch 'upstream/trunk' into dotnet-aot-classic
nvborisenko Oct 18, 2024
3d9763b
ulong
nvborisenko Oct 18, 2024
8d45068
decimal
nvborisenko Oct 18, 2024
9fb9352
Merge branch 'trunk' into dotnet-aot-classic
nvborisenko Oct 19, 2024
5170bc6
Add all built-in types
nvborisenko Oct 19, 2024
c8b6c6d
Merge branch 'dotnet-aot-classic' of https://github.com/nvborisenko/s…
nvborisenko Oct 19, 2024
abdc1af
byte[]
nvborisenko Oct 22, 2024
1d76401
Serialize Enum as decimal
nvborisenko Oct 22, 2024
800a8dd
Enum as long
nvborisenko Oct 22, 2024
287f1f6
Merge remote-tracking branch 'upstream/trunk' into dotnet-aot-classic
nvborisenko Oct 22, 2024
db9aec0
Merge remote-tracking branch 'upstream/trunk' into dotnet-aot-classic
nvborisenko Oct 25, 2024
358cba0
Fix enum converter to long
nvborisenko Oct 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions dotnet/src/webdriver/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ namespace OpenQA.Selenium
/// </summary>
public class Command
{
private SessionId commandSessionId;
private string commandName;
private Dictionary<string, object> commandParameters = new Dictionary<string, object>();

private readonly static JsonSerializerOptions s_jsonSerializerOptions = new()
{
TypeInfoResolver = CommandJsonSerializerContext.Default,
Converters = { new ResponseValueJsonConverter() }
};

private SessionId commandSessionId;
private string commandName;
private Dictionary<string, object> commandParameters = new Dictionary<string, object>();

/// <summary>
/// Initializes a new instance of the <see cref="Command"/> class using a command name and a JSON-encoded string for the parameters.
/// </summary>
Expand Down Expand Up @@ -101,7 +102,7 @@ public string ParametersAsJsonString
string parametersString = string.Empty;
if (this.commandParameters != null && this.commandParameters.Count > 0)
{
parametersString = JsonSerializer.Serialize(this.commandParameters);
parametersString = JsonSerializer.Serialize(this.commandParameters, s_jsonSerializerOptions);
}

if (string.IsNullOrEmpty(parametersString))
Expand Down Expand Up @@ -133,4 +134,34 @@ private static Dictionary<string, object> ConvertParametersFromJson(string value
return parameters;
}
}

// Built-in types
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(byte))]
[JsonSerializable(typeof(sbyte))]
[JsonSerializable(typeof(char))]
[JsonSerializable(typeof(decimal))]
[JsonSerializable(typeof(double))]
[JsonSerializable(typeof(float))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(uint))]
[JsonSerializable(typeof(nint))]
[JsonSerializable(typeof(nuint))]
[JsonSerializable(typeof(long))]
[JsonSerializable(typeof(ulong))]
[JsonSerializable(typeof(short))]
[JsonSerializable(typeof(ushort))]

[JsonSerializable(typeof(string))]

// Selenium WebDriver types
[JsonSerializable(typeof(char[]))]
[JsonSerializable(typeof(byte[]))]
[JsonSerializable(typeof(Dictionary<string, object>))]
[JsonSerializable(typeof(Cookie))]
[JsonSerializable(typeof(Proxy))]
internal partial class CommandJsonSerializerContext : JsonSerializerContext
{

}
}
146 changes: 89 additions & 57 deletions dotnet/src/webdriver/Internal/ResponseValueJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,75 +30,107 @@ internal class ResponseValueJsonConverter : JsonConverter<object>
{
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return this.ProcessToken(ref reader, options);
return ProcessReadToken(ref reader, options);
}

public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, options);
switch (value)
{
case null:
writer.WriteNullValue();
break;
case Enum:
writer.WriteNumberValue(Convert.ToInt64(value));
break;
case IEnumerable<object> list:
writer.WriteStartArray();
foreach (var item in list)
{
Write(writer, item, options);
}
writer.WriteEndArray();
break;
case IDictionary<string, object> dictionary:
writer.WriteStartObject();
foreach (var pair in dictionary)
{
writer.WritePropertyName(pair.Key);
Write(writer, pair.Value, options);
}
writer.WriteEndObject();
break;
case object obj:
JsonSerializer.Serialize(writer, obj, options.GetTypeInfo(obj.GetType()));
break;
}
}

private object ProcessToken(ref Utf8JsonReader reader, JsonSerializerOptions options)
private static object ProcessReadToken(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
// Recursively processes a token. This is required for elements that next other elements.
object processedObject = null;
object processedObject;

if (reader.TokenType == JsonTokenType.StartObject)
switch (reader.TokenType)
{
Dictionary<string, object> dictionaryValue = new Dictionary<string, object>();
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
{
string elementKey = reader.GetString();
reader.Read();
dictionaryValue.Add(elementKey, this.ProcessToken(ref reader, options));
}
case JsonTokenType.StartObject:
{
Dictionary<string, object> dictionaryValue = [];
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
{
string elementKey = reader.GetString();
reader.Read();
dictionaryValue.Add(elementKey, ProcessReadToken(ref reader, options));
}

processedObject = dictionaryValue;
}
else if (reader.TokenType == JsonTokenType.StartArray)
{
List<object> arrayValue = new List<object>();
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
arrayValue.Add(this.ProcessToken(ref reader, options));
}
processedObject = dictionaryValue;
break;
}

processedObject = arrayValue.ToArray();
}
else if (reader.TokenType == JsonTokenType.Null)
{
processedObject = null;
}
else if (reader.TokenType == JsonTokenType.False)
{
processedObject = false;
}
else if (reader.TokenType == JsonTokenType.True)
{
processedObject = true;
}
else if (reader.TokenType == JsonTokenType.String)
{
processedObject = reader.GetString();
}
else if (reader.TokenType == JsonTokenType.Number)
{
if (reader.TryGetInt64(out long longValue))
{
processedObject = longValue;
}
else if (reader.TryGetDouble(out double doubleValue))
{
processedObject = doubleValue;
}
else
{
throw new JsonException($"Unrecognized '{JsonElement.ParseValue(ref reader)}' token as a number value.");
}
}
else
{
throw new JsonException($"Unrecognized '{reader.TokenType}' token type while parsing command response.");
case JsonTokenType.StartArray:
{
List<object> arrayValue = [];
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
{
arrayValue.Add(ProcessReadToken(ref reader, options));
}

processedObject = arrayValue.ToArray();
break;
}

case JsonTokenType.Null:
processedObject = null;
break;
case JsonTokenType.False:
processedObject = false;
break;
case JsonTokenType.True:
processedObject = true;
break;
case JsonTokenType.String:
processedObject = reader.GetString();
break;
case JsonTokenType.Number:
{
if (reader.TryGetInt64(out long longValue))
{
processedObject = longValue;
}
else if (reader.TryGetDouble(out double doubleValue))
{
processedObject = doubleValue;
}
else
{
throw new JsonException($"Unrecognized '{JsonElement.ParseValue(ref reader)}' token as a number value.");
}

break;
}

default:
throw new JsonException($"Unrecognized '{reader.TokenType}' token type while parsing command response.");
}

return processedObject;
Expand Down
10 changes: 9 additions & 1 deletion dotnet/src/webdriver/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace OpenQA.Selenium
{
Expand All @@ -31,7 +32,8 @@ public class Response
{
private readonly static JsonSerializerOptions s_jsonSerializerOptions = new()
{
Converters = { new ResponseValueJsonConverter() }
TypeInfoResolver = ResponseJsonSerializerContext.Default,
Converters = { new ResponseValueJsonConverter() } // we still need it to make `Object` as `Dictionary`
};

private object responseValue;
Expand Down Expand Up @@ -208,4 +210,10 @@ public override string ToString()
return string.Format(CultureInfo.InvariantCulture, "({0} {1}: {2})", this.SessionId, this.Status, this.Value);
}
}

[JsonSerializable(typeof(Dictionary<string, object>))]
internal partial class ResponseJsonSerializerContext : JsonSerializerContext
{

}
}
Loading