Skip to content

Commit

Permalink
[dotnet] Annotate nullability on DevTools and event args (#15252)
Browse files Browse the repository at this point in the history
[dotnet] Annotate nullability `DevTools` and event args
  • Loading branch information
RenderMichael authored Feb 9, 2025
1 parent 8f424e2 commit a44f1d7
Show file tree
Hide file tree
Showing 32 changed files with 306 additions and 237 deletions.
4 changes: 2 additions & 2 deletions dotnet/src/webdriver/Chromium/ChromiumOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -641,9 +641,9 @@ private static Dictionary<string, object> GeneratePerformanceLoggingPreferencesD
return perfLoggingPrefsDictionary;
}

private static Dictionary<string, object> GenerateMobileEmulationSettingsDictionary(ChromiumMobileEmulationDeviceSettings? settings, string? deviceName)
private static Dictionary<string, object?> GenerateMobileEmulationSettingsDictionary(ChromiumMobileEmulationDeviceSettings? settings, string? deviceName)
{
Dictionary<string, object> mobileEmulationSettings = new Dictionary<string, object>();
Dictionary<string, object?> mobileEmulationSettings = new Dictionary<string, object?>();

if (!string.IsNullOrEmpty(deviceName))
{
Expand Down
5 changes: 5 additions & 0 deletions dotnet/src/webdriver/DevTools/DevToolsSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public DevToolsSession(string endpointAddress, DevToolsOptions options)
/// <typeparam name="T">
/// A <see cref="DevToolsSessionDomains"/> object containing the version-specific DevTools Protocol domain implementations.</typeparam>
/// <returns>The version-specific DevTools Protocol domain implementation.</returns>
/// <exception cref="InvalidOperationException">If the provided <typeparamref name="T"/> is not the right protocol version which is running.</exception>
public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
{
if (this.Domains.VersionSpecificDomains is not T versionSpecificDomains)
Expand All @@ -146,6 +147,7 @@ public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="command"/> is <see langword="null"/>.</exception>
public async Task<ICommandResponse<TCommand>?> SendCommand<TCommand>(TCommand command, CancellationToken cancellationToken = default, int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
where TCommand : ICommand
{
Expand Down Expand Up @@ -214,6 +216,7 @@ public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="command"/> is <see langword="null"/>.</exception>
public async Task<TCommandResponse?> SendCommand<TCommand, TCommandResponse>(TCommand command, CancellationToken cancellationToken = default, int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
where TCommand : ICommand
where TCommandResponse : ICommandResponse<TCommand>
Expand Down Expand Up @@ -243,6 +246,7 @@ public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="commandName"/> is <see langword="null"/>.</exception>
public async Task<JsonElement?> SendCommand(string commandName, JsonNode commandParameters, CancellationToken cancellationToken = default, int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
if (this.attachedTargetId == null)
Expand All @@ -264,6 +268,7 @@ public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="commandName"/> is <see langword="null"/>.</exception>
public async Task<JsonElement?> SendCommand(string commandName, string? sessionId, JsonNode commandParameters, CancellationToken cancellationToken = default, int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
millisecondsTimeout ??= Convert.ToInt32(CommandTimeout.TotalMilliseconds);
Expand Down
52 changes: 36 additions & 16 deletions dotnet/src/webdriver/DevTools/DevToolsVersionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

#nullable enable

namespace OpenQA.Selenium.DevTools
{
/// <summary>
Expand All @@ -33,51 +35,69 @@ public class DevToolsVersionInfo
/// </summary>
[JsonPropertyName("Browser")]
[JsonInclude]
public string Browser { get; internal set; }
public string? Browser { get; internal set; }

/// <summary>
/// Gets the browser version without the preceding browser name.
/// </summary>
[JsonIgnore]
public string BrowserVersion => Regex.Match(Browser, ".*/(.*)").Groups[1].Value;
public string BrowserVersion
{
get
{
if (Browser is null)
{
throw new InvalidOperationException("Browser value is null");
}

return Regex.Match(Browser, ".*/(.*)").Groups[1].Value;
}
}

/// <summary>
/// Gets the browser major version number without the preceding browser name.
/// </summary>
[JsonIgnore]
public string BrowserMajorVersion => Regex.Match(Browser, ".*/(\\d+)\\..*").Groups[1].Value;
public string BrowserMajorVersion
{
get
{
if (Browser is null)
{
throw new InvalidOperationException("Browser value is null");
}

return Regex.Match(Browser, ".*/(\\d+)\\..*").Groups[1].Value;
}
}

/// <summary>
/// Gets the version of the Developer Tools Protocol.
/// </summary>
[JsonPropertyName("Protocol-Version")]
[JsonInclude]
public string ProtocolVersion { get; internal set; }
public string? ProtocolVersion { get; internal set; }

/// <summary>
/// Gets the user agent string.
/// </summary>
[JsonPropertyName("User-Agent")]
[JsonInclude]
public string UserAgent { get; internal set; }
public string? UserAgent { get; internal set; }

/// <summary>
/// Gets the version string for the V8 script engine in use by this version of the browser.
/// </summary>
[JsonPropertyName("V8-Version")]
[JsonInclude]
public string V8Version
{
get;
internal set;
}
public string? V8Version { get; internal set; }

/// <summary>
/// Gets the URL for the WebSocket connection used for communicating via the DevTools Protocol.
/// </summary>
[JsonPropertyName("webSocketDebuggerUrl")]
[JsonInclude]
public string WebSocketDebuggerUrl { get; internal set; }
public string? WebSocketDebuggerUrl { get; internal set; }

/// <summary>
/// Gets the version number of the V8 script engine, stripping values other than the version number.
Expand All @@ -88,8 +108,8 @@ public string V8VersionNumber
get
{
//Get the v8 version
var v8VersionMatch = Regex.Match(V8Version, @"^(\d+)\.(\d+)\.(\d+)(\.\d+.*)?");
if (v8VersionMatch.Success == false || v8VersionMatch.Groups.Count < 4)
Match? v8VersionMatch = V8Version is null ? null : Regex.Match(V8Version, @"^(\d+)\.(\d+)\.(\d+)(\.\d+.*)?");
if (v8VersionMatch is null || v8VersionMatch.Success == false || v8VersionMatch.Groups.Count < 4)
{
throw new InvalidOperationException($"Unable to determine v8 version number from v8 version string ({V8Version})");
}
Expand All @@ -103,7 +123,7 @@ public string V8VersionNumber
/// </summary>
[JsonPropertyName("WebKit-Version")]
[JsonInclude]
public string WebKitVersion { get; internal set; }
public string? WebKitVersion { get; internal set; }

/// <summary>
/// Gets the hash of the version of WebKit, stripping values other than the hash.
Expand All @@ -114,8 +134,8 @@ public string WebKitVersionHash
get
{
//Get the webkit version hash.
var webkitVersionMatch = Regex.Match(WebKitVersion, @"\s\(@(\b[0-9a-f]{5,40}\b)");
if (webkitVersionMatch.Success == false || webkitVersionMatch.Groups.Count != 2)
var webkitVersionMatch = WebKitVersion is null ? null : Regex.Match(WebKitVersion, @"\s\(@(\b[0-9a-f]{5,40}\b)");
if (webkitVersionMatch is null || webkitVersionMatch.Success == false || webkitVersionMatch.Groups.Count != 2)
{
throw new InvalidOperationException($"Unable to determine webkit version hash from webkit version string ({WebKitVersion})");
}
Expand Down
15 changes: 5 additions & 10 deletions dotnet/src/webdriver/DevTools/ICommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
// under the License.
// </copyright>

#nullable enable

namespace OpenQA.Selenium.DevTools
{
/// <summary>
Expand All @@ -27,24 +29,17 @@ public interface ICommand
/// <summary>
/// Gets the name of the command.
/// </summary>
string CommandName
{
get;
}
string CommandName { get; }
}

/// <summary>
/// Represents a response to a command submitted by the DevTools Remote Interface
///</summary>
public interface ICommandResponse
{
}
public interface ICommandResponse;

/// <summary>
/// Represents a response to a command submitted by the DevTools Remote Interface
///</summary>
public interface ICommandResponse<T> : ICommandResponse
where T : ICommand
{
}
where T : ICommand;
}
3 changes: 3 additions & 0 deletions dotnet/src/webdriver/DevTools/IDevTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

using System;

#nullable enable

namespace OpenQA.Selenium.DevTools
{
/// <summary>
Expand All @@ -42,6 +44,7 @@ public interface IDevTools
/// </summary>
/// <param name="options">The options for the DevToolsSession to use.</param>
/// <returns>The active session to use to communicate with the Developer Tools debugging protocol.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="options"/> is <see langword="null"/>.</exception>
DevToolsSession GetDevToolsSession(DevToolsOptions options);

/// <summary>
Expand Down
6 changes: 5 additions & 1 deletion dotnet/src/webdriver/DevTools/IDevToolsSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace OpenQA.Selenium.DevTools
{
/// <summary>
/// Represents a WebSocket connection to a running DevTools instance that can be used to send
/// commands and recieve events.
/// commands and receive events.
///</summary>
public interface IDevToolsSession : IDisposable
{
Expand All @@ -50,6 +50,7 @@ public interface IDevToolsSession : IDisposable
/// A <see cref="DevToolsSessionDomains"/> type specific to the version of Developer Tools with which to communicate.
/// </typeparam>
/// <returns>The version-specific domains for this Developer Tools connection.</returns>
/// <exception cref="InvalidOperationException">If the provided <typeparamref name="T"/> is not the right protocol version which is running.</exception>
T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains;

/// <summary>
Expand All @@ -61,6 +62,7 @@ public interface IDevToolsSession : IDisposable
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="command"/> is <see langword="null"/>.</exception>
Task<ICommandResponse<TCommand>?> SendCommand<TCommand>(TCommand command, CancellationToken cancellationToken, int? millisecondsTimeout, bool throwExceptionIfResponseNotReceived)
where TCommand : ICommand;

Expand All @@ -74,6 +76,7 @@ public interface IDevToolsSession : IDisposable
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="command"/> is <see langword="null"/>.</exception>
Task<TCommandResponse?> SendCommand<TCommand, TCommandResponse>(TCommand command, CancellationToken cancellationToken, int? millisecondsTimeout, bool throwExceptionIfResponseNotReceived)
where TCommand : ICommand
where TCommandResponse : ICommandResponse<TCommand>;
Expand All @@ -87,6 +90,7 @@ public interface IDevToolsSession : IDisposable
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="commandName"/> is <see langword="null"/>.</exception>
Task<JsonElement?> SendCommand(string commandName, JsonNode @params, CancellationToken cancellationToken, int? millisecondsTimeout, bool throwExceptionIfResponseNotReceived);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public JsonEnumMemberConverter()
#endif
foreach (var value in values)
{
var enumMember = type.GetField(value.ToString());
var enumMember = type.GetField(value.ToString())!;
var attr = enumMember.GetCustomAttributes(typeof(EnumMemberAttribute), false)
.Cast<EnumMemberAttribute>()
.FirstOrDefault();
Expand Down
32 changes: 21 additions & 11 deletions dotnet/src/webdriver/DevTools/WebSocketConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
using System.Threading;
using System.Threading.Tasks;

#nullable enable

namespace OpenQA.Selenium.DevTools
{
/// <summary>
Expand All @@ -34,9 +36,7 @@ public class WebSocketConnection
private readonly CancellationTokenSource clientTokenSource = new CancellationTokenSource();
private readonly TimeSpan startupTimeout;
private readonly TimeSpan shutdownTimeout;
private readonly int bufferSize = 4096;
private Task dataReceiveTask;
private bool isActive = false;
private Task? dataReceiveTask;
private ClientWebSocket client = new ClientWebSocket();
private readonly SemaphoreSlim sendMethodSemaphore = new SemaphoreSlim(1, 1);

Expand Down Expand Up @@ -71,22 +71,22 @@ public WebSocketConnection(TimeSpan startupTimeout, TimeSpan shutdownTimeout)
/// <summary>
/// Occurs when data is received from this connection.
/// </summary>
public event EventHandler<WebSocketConnectionDataReceivedEventArgs> DataReceived;
public event EventHandler<WebSocketConnectionDataReceivedEventArgs>? DataReceived;

/// <summary>
/// Occurs when a log message is emitted from this connection.
/// </summary>
public event EventHandler<DevToolsSessionLogMessageEventArgs> LogMessage;
public event EventHandler<DevToolsSessionLogMessageEventArgs>? LogMessage;

/// <summary>
/// Gets a value indicating whether this connection is active.
/// </summary>
public bool IsActive => this.isActive;
public bool IsActive { get; private set; } = false;

/// <summary>
/// Gets the buffer size for communication used by this connection.
/// </summary>
public int BufferSize => this.bufferSize;
public int BufferSize { get; } = 4096;

/// <summary>
/// Asynchronously starts communication with the remote end of this connection.
Expand All @@ -96,6 +96,11 @@ public WebSocketConnection(TimeSpan startupTimeout, TimeSpan shutdownTimeout)
/// <exception cref="TimeoutException">Thrown when the connection is not established within the startup timeout.</exception>
public virtual async Task Start(string url)
{
if (url is null)
{
throw new ArgumentNullException(nameof(url));
}

this.Log($"Opening connection to URL {url}", DevToolsSessionLogLevel.Trace);
bool connected = false;
DateTime timeout = DateTime.Now.Add(this.startupTimeout);
Expand All @@ -121,7 +126,7 @@ public virtual async Task Start(string url)
}

this.dataReceiveTask = Task.Run(async () => await this.ReceiveData());
this.isActive = true;
this.IsActive = true;
this.Log($"Connection opened", DevToolsSessionLogLevel.Trace);
}

Expand Down Expand Up @@ -159,6 +164,11 @@ public virtual async Task Stop()
/// <returns>The task object representing the asynchronous operation.</returns>
public virtual async Task SendData(string data)
{
if (data is null)
{
throw new ArgumentNullException(nameof(data));
}

ArraySegment<byte> messageBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(data));
this.Log($"SEND >>> {data}");

Expand Down Expand Up @@ -236,7 +246,7 @@ private async Task ReceiveData()
try
{
StringBuilder messageBuilder = new StringBuilder();
ArraySegment<byte> buffer = WebSocket.CreateClientBuffer(this.bufferSize, this.bufferSize);
ArraySegment<byte> buffer = WebSocket.CreateClientBuffer(this.BufferSize, this.BufferSize);
while (this.client.State != WebSocketState.Closed && !cancellationToken.IsCancellationRequested)
{
WebSocketReceiveResult receiveResult = await this.client.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false);
Expand All @@ -255,7 +265,7 @@ private async Task ReceiveData()
// Display text or binary data
if (this.client.State == WebSocketState.Open && receiveResult.MessageType != WebSocketMessageType.Close)
{
messageBuilder.Append(Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count));
messageBuilder.Append(Encoding.UTF8.GetString(buffer.Array!, 0, receiveResult.Count));
if (receiveResult.EndOfMessage)
{
string message = messageBuilder.ToString();
Expand All @@ -282,7 +292,7 @@ private async Task ReceiveData()
}
finally
{
this.isActive = false;
this.IsActive = false;
}
}

Expand Down
Loading

0 comments on commit a44f1d7

Please sign in to comment.