Skip to content

[dotnet] Annotate nullability on DevTools and event args #15252

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

Merged
merged 2 commits into from
Feb 9, 2025
Merged
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
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
Loading