Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace OpenQA.Selenium.BiDi.Communication.Json;

[JsonSerializable(typeof(Modules.Script.RemoteValue.Number), TypeInfoPropertyName = "Script_RemoteValue_Number")]
[JsonSerializable(typeof(Modules.Script.RemoteValue.Boolean), TypeInfoPropertyName = "Script_RemoteValue_Boolean")]
[JsonSerializable(typeof(Modules.Script.RemoteValue.BigInt), TypeInfoPropertyName = "Script_RemoteValue_BigInt")]
[JsonSerializable(typeof(Modules.Script.RemoteValue.String), TypeInfoPropertyName = "Script_RemoteValue_String")]
[JsonSerializable(typeof(Modules.Script.RemoteValue.Null), TypeInfoPropertyName = "Script_RemoteValue_Null")]
[JsonSerializable(typeof(Modules.Script.RemoteValue.Undefined), TypeInfoPropertyName = "Script_RemoteValue_Undefined")]
Expand Down Expand Up @@ -141,6 +142,8 @@ namespace OpenQA.Selenium.BiDi.Communication.Json;
[JsonSerializable(typeof(Modules.Network.AuthRequiredEventArgs))]

[JsonSerializable(typeof(Modules.Script.Channel), TypeInfoPropertyName = "Script_Channel")]
[JsonSerializable(typeof(Modules.Script.LocalValue.Boolean), TypeInfoPropertyName = "Script_LocalValue_Boolean")]
[JsonSerializable(typeof(Modules.Script.LocalValue.BigInt), TypeInfoPropertyName = "Script_LocalValue_BigInt")]
[JsonSerializable(typeof(Modules.Script.LocalValue.String), TypeInfoPropertyName = "Script_LocalValue_String")]
[JsonSerializable(typeof(Modules.Script.Target.Realm), TypeInfoPropertyName = "Script_Target_Realm")]
[JsonSerializable(typeof(Modules.Script.Target.Context), TypeInfoPropertyName = "Script_Target_Context")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ internal class RemoteValueConverter : JsonConverter<RemoteValue>
{
"number" => jsonDocument.Deserialize<RemoteValue.Number>(options),
"boolean" => jsonDocument.Deserialize<RemoteValue.Boolean>(options),
"bigint" => jsonDocument.Deserialize<RemoteValue.BigInt>(options),
"string" => jsonDocument.Deserialize<RemoteValue.String>(options),
"null" => jsonDocument.Deserialize<RemoteValue.Null>(options),
"undefined" => jsonDocument.Deserialize<RemoteValue.Undefined>(options),
Expand Down
168 changes: 165 additions & 3 deletions dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,21 @@
// under the License.
// </copyright>

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace OpenQA.Selenium.BiDi.Modules.Script;

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(Boolean), "boolean")]
[JsonDerivedType(typeof(BigInt), "bigint")]
[JsonDerivedType(typeof(Number), "number")]
[JsonDerivedType(typeof(String), "string")]
[JsonDerivedType(typeof(Null), "null")]
Expand All @@ -37,7 +46,7 @@ namespace OpenQA.Selenium.BiDi.Modules.Script;
public abstract record LocalValue
{
public static implicit operator LocalValue(int value) { return new Number(value); }
public static implicit operator LocalValue(string value) { return new String(value); }
public static implicit operator LocalValue(string? value) { return value is null ? new Null() : new String(value); }

// TODO: Extend converting from types
public static LocalValue ConvertFrom(object? value)
Expand Down Expand Up @@ -70,14 +79,86 @@ public static LocalValue ConvertFrom(object? value)
}
}

public static LocalValue FromNode(JsonNode? node)
{
if (node is null)
{
return new Null();
}

switch (node.GetValueKind())
{
case JsonValueKind.True:
return new Boolean(true);

case JsonValueKind.False:
return new Boolean(false);

case JsonValueKind.Number:
{
JsonValue value = node.AsValue();
if (value.TryGetValue(out int intValue))
{
return new Number(intValue);
}

if (value.TryGetValue(out double doubleValue) && !double.IsInfinity(doubleValue))
{
return new Number(doubleValue);
}

return new BigInt(BigInteger.Parse(value.ToJsonString()));
}

case JsonValueKind.String:
return new String(node.GetValue<string>());

case JsonValueKind.Array:
return new Array(node.AsArray().Select(FromNode));

case JsonValueKind.Object:
Dictionary<string, LocalValue> values = node.AsObject().ToDictionary(node => node.Key, node => FromNode(node.Value));
return Object.FromDictionary(values);

default:
throw new InvalidCastException($"Could not convert node {node}");
}
}

public abstract record PrimitiveProtocolLocalValue : LocalValue
{

}

public record BigInt : PrimitiveProtocolLocalValue
{
[JsonIgnore]
public BigInteger Value { get; }

[JsonInclude]
[JsonPropertyName("value")]
public string ValueAsString => Value.ToString();

public BigInt(BigInteger value)
{
Value = value;
}

[JsonConstructor]
internal BigInt(string valueAsString)
{
Value = BigInteger.Parse(valueAsString);
}
}

public record Boolean(bool Value) : PrimitiveProtocolLocalValue;

public record Number(double Value) : PrimitiveProtocolLocalValue
{
public static explicit operator Number(double n) => new Number(n);
public static Number PositiveInfinity { get; } = new Number(double.PositiveInfinity);
public static Number NegativeInfinity { get; } = new Number(double.NegativeInfinity);
public static Number NaN { get; } = new Number(double.NaN);
}

public record String(string Value) : PrimitiveProtocolLocalValue;
Expand All @@ -101,18 +182,99 @@ public record ChannelProperties(Script.Channel Channel)

public record Array(IEnumerable<LocalValue> Value) : LocalValue;

public record Date(string Value) : LocalValue;
public record Date(string Value) : LocalValue
{
public static Date FromDateTime(DateTime value)
{
return new Date(value.ToString("o"));
}
}

public record Map(IDictionary<string, LocalValue> Value) : LocalValue; // seems to implement IDictionary

public record Object(IEnumerable<IEnumerable<LocalValue>> Value) : LocalValue;
public record Object(IEnumerable<IEnumerable<LocalValue>> Value) : LocalValue
{
public static Object FromDictionary(IDictionary<string, LocalValue> values)
{
return new Object([.. values.Select(PairAsList)]);
}

private static IEnumerable<LocalValue> PairAsList(KeyValuePair<string, LocalValue> pair)
{
return [new String(pair.Key), pair.Value ?? new Null()];
}
}

public record RegExp(RegExp.RegExpValue Value) : LocalValue
{
public record RegExpValue(string Pattern)
{
public string? Flags { get; set; }
}

/// <summary>
/// Converts a .NET Regex into a BiDi Regex
/// </summary>
/// <param name="regex">A .NET Regex.</param>
/// <returns>A BiDi Regex.</returns>
/// <remarks>
/// Note that the .NET regular expression engine does not work the same as the JavaScript engine.
/// To minimize the differences between the two engines, it is recommended to enabled the <see cref="RegexOptions.ECMAScript"/> option.
/// </remarks>
public static RegExp FromRegex(Regex regex)
{
RegexOptions options = regex.Options;

if (options == RegexOptions.None)
{
return new RegExp(new RegExpValue(regex.ToString()));
}

string flags = string.Empty;

const RegexOptions NonBacktracking = (RegexOptions)1024;
#if NET8_0_OR_GREATER
Debug.Assert(NonBacktracking == RegexOptions.NonBacktracking);
#endif

const RegexOptions NonApplicableOptions = RegexOptions.Compiled | NonBacktracking;

const RegexOptions UnsupportedOptions =
RegexOptions.ExplicitCapture |
RegexOptions.IgnorePatternWhitespace |
RegexOptions.RightToLeft |
RegexOptions.CultureInvariant;

options &= ~NonApplicableOptions;

if ((options & UnsupportedOptions) != 0)
{
throw new NotSupportedException($"The selected RegEx options are not supported in BiDi: {options & UnsupportedOptions}");
}

if ((options & RegexOptions.IgnoreCase) != 0)
{
flags += "i";
options = options & ~RegexOptions.IgnoreCase;
}

if ((options & RegexOptions.Multiline) != 0)
{
options = options & ~RegexOptions.Multiline;
flags += "m";
}

if ((options & RegexOptions.Singleline) != 0)
{
options = options & ~RegexOptions.Singleline;
flags += "s";
}

Debug.Assert(options == RegexOptions.None);

return new RegExp(new RegExpValue(regex.ToString()) { Flags = flags });

}
}

public record Set(IEnumerable<LocalValue> Value) : LocalValue;
Expand Down
23 changes: 23 additions & 0 deletions dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand All @@ -28,6 +29,7 @@ namespace OpenQA.Selenium.BiDi.Modules.Script;
//[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
//[JsonDerivedType(typeof(Number), "number")]
//[JsonDerivedType(typeof(Boolean), "boolean")]
//[JsonDerivedType(typeof(BigInt), "bigint")]
//[JsonDerivedType(typeof(String), "string")]
//[JsonDerivedType(typeof(Null), "null")]
//[JsonDerivedType(typeof(Undefined), "undefined")]
Expand Down Expand Up @@ -95,6 +97,27 @@ public record Number(double Value) : PrimitiveProtocolRemoteValue;

public record Boolean(bool Value) : PrimitiveProtocolRemoteValue;

public record BigInt : PrimitiveProtocolRemoteValue
{
[JsonIgnore]
public BigInteger Value { get; }

[JsonInclude]
[JsonPropertyName("value")]
public string ValueAsString => Value.ToString();

public BigInt(BigInteger value)
{
Value = value;
}

[JsonConstructor]
internal BigInt(string valueAsString)
{
Value = BigInteger.Parse(valueAsString);
}
}

public record String(string Value) : PrimitiveProtocolRemoteValue;

public record Null : PrimitiveProtocolRemoteValue;
Expand Down
Loading