diff --git a/dotnet/src/webdriver/BUILD.bazel b/dotnet/src/webdriver/BUILD.bazel index afcdd0785bae4..2b410b83df650 100644 --- a/dotnet/src/webdriver/BUILD.bazel +++ b/dotnet/src/webdriver/BUILD.bazel @@ -55,6 +55,7 @@ csharp_library( framework("nuget", "Microsoft.Bcl.AsyncInterfaces"), framework("nuget", "System.Threading.Tasks.Extensions"), framework("nuget", "System.Memory"), + framework("nuget", "System.Runtime.CompilerServices.Unsafe"), framework("nuget", "System.Text.Encodings.Web"), framework("nuget", "System.Text.Json"), ], @@ -120,6 +121,7 @@ csharp_library( framework("nuget", "Microsoft.Bcl.AsyncInterfaces"), framework("nuget", "System.Threading.Tasks.Extensions"), framework("nuget", "System.Memory"), + framework("nuget", "System.Runtime.CompilerServices.Unsafe"), framework("nuget", "System.Text.Encodings.Web"), framework("nuget", "System.Text.Json"), ], diff --git a/dotnet/src/webdriver/BiDi/Communication/Broker.cs b/dotnet/src/webdriver/BiDi/Communication/Broker.cs index b2dc03942d7ee..8a6466c9f17e4 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Broker.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Broker.cs @@ -17,6 +17,7 @@ // under the License. // +using OpenQA.Selenium.BiDi.Communication.Json; using OpenQA.Selenium.BiDi.Communication.Json.Converters; using OpenQA.Selenium.BiDi.Communication.Transport; using OpenQA.Selenium.Internal.Logging; @@ -53,14 +54,14 @@ public class Broker : IAsyncDisposable private Task? _eventEmitterTask; private CancellationTokenSource? _receiveMessagesCancellationTokenSource; - private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly BiDiJsonSerializerContext _jsonSerializerContext; internal Broker(BiDi bidi, ITransport transport) { _bidi = bidi; _transport = transport; - _jsonSerializerOptions = new JsonSerializerOptions + var jsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -99,6 +100,8 @@ internal Broker(BiDi bidi, ITransport transport) new Json.Converters.Enumerable.GetRealmsResultConverter(), } }; + + _jsonSerializerContext = new BiDiJsonSerializerContext(jsonSerializerOptions); } public async Task ConnectAsync(CancellationToken cancellationToken) @@ -114,7 +117,7 @@ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { - var message = await _transport.ReceiveAsJsonAsync(_jsonSerializerOptions, cancellationToken); + var message = await _transport.ReceiveAsJsonAsync(_jsonSerializerContext, cancellationToken); switch (message) { @@ -145,7 +148,7 @@ private async Task ProcessEventsAwaiterAsync() { foreach (var handler in eventHandlers.ToArray()) // copy handlers avoiding modified collection while iterating { - var args = (EventArgs)result.Params.Deserialize(handler.EventArgsType, _jsonSerializerOptions)!; + var args = (EventArgs)result.Params.Deserialize(handler.EventArgsType, _jsonSerializerContext)!; args.BiDi = _bidi; @@ -177,7 +180,7 @@ public async Task ExecuteCommandAsync(Command command, Command { var result = await ExecuteCommandCoreAsync(command, options).ConfigureAwait(false); - return (TResult)((JsonElement)result).Deserialize(typeof(TResult), _jsonSerializerOptions)!; + return (TResult)((JsonElement)result).Deserialize(typeof(TResult), _jsonSerializerContext)!; } public async Task ExecuteCommandAsync(Command command, CommandOptions? options) @@ -199,7 +202,7 @@ private async Task ExecuteCommandCoreAsync(Command command, CommandOptio _pendingCommands[command.Id] = tcs; - await _transport.SendAsJsonAsync(command, _jsonSerializerOptions, cts.Token).ConfigureAwait(false); + await _transport.SendAsJsonAsync(command, _jsonSerializerContext, cts.Token).ConfigureAwait(false); return await tcs.Task.ConfigureAwait(false); } diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs new file mode 100644 index 0000000000000..8d55b8c89c0c6 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs @@ -0,0 +1,135 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json; + +#region https://github.com/dotnet/runtime/issues/72604 +[JsonSerializable(typeof(MessageSuccess))] +[JsonSerializable(typeof(MessageError))] +[JsonSerializable(typeof(MessageEvent))] + +[JsonSerializable(typeof(Modules.Script.EvaluateResult.Success))] +[JsonSerializable(typeof(Modules.Script.EvaluateResult.Exception))] + +[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.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")] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Symbol))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Array), TypeInfoPropertyName = "Script_RemoteValue_Array")] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Object), TypeInfoPropertyName = "Script_RemoteValue_Object")] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Function))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.RegExp), TypeInfoPropertyName = "Script_RemoteValue_RegExp")] +[JsonSerializable(typeof(Modules.Script.RemoteValue.RegExp.RegExpValue), TypeInfoPropertyName = "Script_RemoteValue_RegExp_RegExpValue")] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Date), TypeInfoPropertyName = "Script_RemoteValue_Date")] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Map), TypeInfoPropertyName = "Script_RemoteValue_Map")] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Set), TypeInfoPropertyName = "Script_RemoteValue_Set")] +[JsonSerializable(typeof(Modules.Script.RemoteValue.WeakMap))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.WeakSet))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Generator))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Error))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Proxy))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Promise))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.TypedArray))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.ArrayBuffer))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.NodeList))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.HtmlCollection))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.Node))] +[JsonSerializable(typeof(Modules.Script.RemoteValue.WindowProxy))] + +[JsonSerializable(typeof(Modules.Script.RealmInfo.Window))] +[JsonSerializable(typeof(Modules.Script.RealmInfo.DedicatedWorker))] +[JsonSerializable(typeof(Modules.Script.RealmInfo.SharedWorker))] +[JsonSerializable(typeof(Modules.Script.RealmInfo.ServiceWorker))] +[JsonSerializable(typeof(Modules.Script.RealmInfo.Worker))] +[JsonSerializable(typeof(Modules.Script.RealmInfo.PaintWorklet))] +[JsonSerializable(typeof(Modules.Script.RealmInfo.AudioWorklet))] +[JsonSerializable(typeof(Modules.Script.RealmInfo.Worklet))] + +[JsonSerializable(typeof(Modules.Log.Entry.Console))] +[JsonSerializable(typeof(Modules.Log.Entry.Javascript))] +#endregion + +[JsonSerializable(typeof(Command))] +[JsonSerializable(typeof(Message))] + +[JsonSerializable(typeof(Modules.Session.StatusResult))] +[JsonSerializable(typeof(Modules.Session.NewResult))] + +[JsonSerializable(typeof(Modules.Browser.CloseCommand), TypeInfoPropertyName = "Browser_CloseCommand")] +[JsonSerializable(typeof(Modules.Browser.GetUserContextsResult))] +[JsonSerializable(typeof(IReadOnlyList))] + +[JsonSerializable(typeof(Modules.BrowsingContext.CloseCommand), TypeInfoPropertyName = "BrowsingContext_CloseCommand")] +[JsonSerializable(typeof(Modules.BrowsingContext.CreateResult))] +[JsonSerializable(typeof(Modules.BrowsingContext.BrowsingContextInfo))] +[JsonSerializable(typeof(Modules.BrowsingContext.NavigateResult))] +[JsonSerializable(typeof(Modules.BrowsingContext.NavigationInfo))] +[JsonSerializable(typeof(Modules.BrowsingContext.TraverseHistoryResult))] +[JsonSerializable(typeof(Modules.BrowsingContext.LocateNodesResult))] +[JsonSerializable(typeof(Modules.BrowsingContext.CaptureScreenshotResult))] +[JsonSerializable(typeof(Modules.BrowsingContext.GetTreeResult))] +[JsonSerializable(typeof(Modules.BrowsingContext.PrintResult))] +[JsonSerializable(typeof(Modules.BrowsingContext.UserPromptOpenedEventArgs))] +[JsonSerializable(typeof(Modules.BrowsingContext.UserPromptClosedEventArgs))] +[JsonSerializable(typeof(Modules.BrowsingContext.Origin), TypeInfoPropertyName = "BrowsingContext_Origin")] + +[JsonSerializable(typeof(Modules.Network.BytesValue.String), TypeInfoPropertyName = "Network_BytesValue_String")] +[JsonSerializable(typeof(Modules.Network.UrlPattern.String), TypeInfoPropertyName = "Network_UrlPattern_String")] +[JsonSerializable(typeof(Modules.Network.ContinueWithAuthParameters.Default), TypeInfoPropertyName = "Network_ContinueWithAuthParameters_Default")] +[JsonSerializable(typeof(Modules.Network.AddInterceptResult))] +[JsonSerializable(typeof(Modules.Network.BeforeRequestSentEventArgs))] +[JsonSerializable(typeof(Modules.Network.ResponseStartedEventArgs))] +[JsonSerializable(typeof(Modules.Network.ResponseCompletedEventArgs))] +[JsonSerializable(typeof(Modules.Network.FetchErrorEventArgs))] +[JsonSerializable(typeof(Modules.Network.AuthRequiredEventArgs))] + +[JsonSerializable(typeof(Modules.Script.Channel), TypeInfoPropertyName = "Script_Channel")] +[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")] +[JsonSerializable(typeof(Modules.Script.AddPreloadScriptResult))] +[JsonSerializable(typeof(Modules.Script.EvaluateResult))] +[JsonSerializable(typeof(Modules.Script.GetRealmsResult))] +[JsonSerializable(typeof(Modules.Script.MessageEventArgs))] +[JsonSerializable(typeof(Modules.Script.RealmDestroyedEventArgs))] +[JsonSerializable(typeof(IReadOnlyList))] + +[JsonSerializable(typeof(Modules.Log.Entry))] + +[JsonSerializable(typeof(Modules.Storage.GetCookiesResult))] +[JsonSerializable(typeof(Modules.Storage.DeleteCookiesResult))] +[JsonSerializable(typeof(Modules.Storage.SetCookieResult))] + +[JsonSerializable(typeof(Modules.Input.PerformActionsCommand))] +[JsonSerializable(typeof(Modules.Input.Pointer.Down), TypeInfoPropertyName = "Input_Pointer_Down")] +[JsonSerializable(typeof(Modules.Input.Pointer.Up), TypeInfoPropertyName = "Input_Pointer_Up")] +[JsonSerializable(typeof(Modules.Input.Pointer.Move), TypeInfoPropertyName = "Input_Pointer_Move")] +[JsonSerializable(typeof(Modules.Input.Key.Down), TypeInfoPropertyName = "Input_Key_Down")] +[JsonSerializable(typeof(Modules.Input.Key.Up), TypeInfoPropertyName = "Input_Key_Up")] +[JsonSerializable(typeof(IEnumerable))] +[JsonSerializable(typeof(IEnumerable))] +[JsonSerializable(typeof(IEnumerable))] +[JsonSerializable(typeof(IEnumerable))] + +internal partial class BiDiJsonSerializerContext : JsonSerializerContext; diff --git a/dotnet/src/webdriver/BiDi/Communication/Transport/ITransport.cs b/dotnet/src/webdriver/BiDi/Communication/Transport/ITransport.cs index ebcb0ebea8b46..02d5cc9422645 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Transport/ITransport.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Transport/ITransport.cs @@ -17,10 +17,10 @@ // under the License. // -using System.Text.Json; using System.Threading.Tasks; using System.Threading; using System; +using System.Text.Json.Serialization; #nullable enable @@ -30,7 +30,7 @@ interface ITransport : IDisposable { Task ConnectAsync(CancellationToken cancellationToken); - Task ReceiveAsJsonAsync(JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken); + Task ReceiveAsJsonAsync(JsonSerializerContext jsonSerializerContext, CancellationToken cancellationToken); - Task SendAsJsonAsync(Command command, JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken); + Task SendAsJsonAsync(Command command, JsonSerializerContext jsonSerializerContext, CancellationToken cancellationToken); } diff --git a/dotnet/src/webdriver/BiDi/Communication/Transport/WebSocketTransport.cs b/dotnet/src/webdriver/BiDi/Communication/Transport/WebSocketTransport.cs index f5b1aeaf82a3c..643c4ebb2d96b 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Transport/WebSocketTransport.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Transport/WebSocketTransport.cs @@ -25,6 +25,7 @@ using System.Text.Json; using System.Text; using OpenQA.Selenium.Internal.Logging; +using System.Text.Json.Serialization; #nullable enable @@ -44,7 +45,7 @@ public async Task ConnectAsync(CancellationToken cancellationToken) await _webSocket.ConnectAsync(_uri, cancellationToken).ConfigureAwait(false); } - public async Task ReceiveAsJsonAsync(JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken) + public async Task ReceiveAsJsonAsync(JsonSerializerContext jsonSerializerContext, CancellationToken cancellationToken) { using var ms = new MemoryStream(); @@ -65,14 +66,14 @@ public async Task ReceiveAsJsonAsync(JsonSerializerOptions jsonSerializerO _logger.Trace($"BiDi RCV << {Encoding.UTF8.GetString(ms.ToArray())}"); } - var res = await JsonSerializer.DeserializeAsync(ms, typeof(T), jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + var res = await JsonSerializer.DeserializeAsync(ms, typeof(T), jsonSerializerContext, cancellationToken).ConfigureAwait(false); return (T)res!; } - public async Task SendAsJsonAsync(Command command, JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken) + public async Task SendAsJsonAsync(Command command, JsonSerializerContext jsonSerializerContext, CancellationToken cancellationToken) { - var buffer = JsonSerializer.SerializeToUtf8Bytes(command, typeof(Command), jsonSerializerOptions); + var buffer = JsonSerializer.SerializeToUtf8Bytes(command, typeof(Command), jsonSerializerContext); await _socketSendSemaphoreSlim.WaitAsync(cancellationToken);