diff --git a/dotnet/src/webdriver/CommandInfo.cs b/dotnet/src/webdriver/CommandInfo.cs index 0e679e221dcc7..a1bde2f141a20 100644 --- a/dotnet/src/webdriver/CommandInfo.cs +++ b/dotnet/src/webdriver/CommandInfo.cs @@ -19,6 +19,8 @@ using System; +#nullable enable + namespace OpenQA.Selenium { /// @@ -45,7 +47,7 @@ public override int GetHashCode() /// /// The to compare to this instance. /// if is a and its value is the same as this instance; otherwise, . If is , the method returns . - public override bool Equals(object obj) + public override bool Equals(object? obj) { return this.Equals(obj as CommandInfo); } @@ -55,7 +57,7 @@ public override bool Equals(object obj) /// /// The to compare to this instance. /// if the value of the parameter is the same as this instance; otherwise, . If is , the method returns . - public bool Equals(CommandInfo other) + public bool Equals(CommandInfo? other) { if (other is null) { @@ -63,7 +65,7 @@ public bool Equals(CommandInfo other) } // Optimization for a common success case. - if (Object.ReferenceEquals(this, other)) + if (object.ReferenceEquals(this, other)) { return true; } @@ -86,7 +88,7 @@ public bool Equals(CommandInfo other) /// The first object to compare. /// The second object to compare. /// if the value of is the same as the value of ; otherwise, . - public static bool operator ==(CommandInfo left, CommandInfo right) + public static bool operator ==(CommandInfo? left, CommandInfo? right) { if (left is null) { @@ -107,7 +109,7 @@ public bool Equals(CommandInfo other) /// The first object to compare. /// The second object to compare. /// if the value of is different from the value of ; otherwise, . - public static bool operator !=(CommandInfo left, CommandInfo right) + public static bool operator !=(CommandInfo? left, CommandInfo? right) { return !(left == right); } diff --git a/dotnet/src/webdriver/HttpCommandInfo.cs b/dotnet/src/webdriver/HttpCommandInfo.cs index 0841ffcde7524..36965d875cd5b 100644 --- a/dotnet/src/webdriver/HttpCommandInfo.cs +++ b/dotnet/src/webdriver/HttpCommandInfo.cs @@ -20,6 +20,8 @@ using System; using System.Globalization; +#nullable enable + namespace OpenQA.Selenium { /// @@ -44,9 +46,6 @@ public class HttpCommandInfo : CommandInfo private const string SessionIdPropertyName = "sessionId"; - private string resourcePath; - private string method; - /// /// Initializes a new instance of the class /// @@ -54,32 +53,26 @@ public class HttpCommandInfo : CommandInfo /// Relative URL path to the resource used to execute the command public HttpCommandInfo(string method, string resourcePath) { - this.resourcePath = resourcePath; - this.method = method; + this.ResourcePath = resourcePath; + this.Method = method; } /// /// Gets the URL representing the path to the resource. /// - public string ResourcePath - { - get { return this.resourcePath; } - } + public string ResourcePath { get; } /// /// Gets the HTTP method associated with the command. /// - public string Method - { - get { return this.method; } - } + public string Method { get; } /// /// Gets the unique identifier for this command within the scope of its protocol definition /// public override string CommandIdentifier { - get { return string.Format(CultureInfo.InvariantCulture, "{0} {1}", this.method, this.resourcePath); } + get { return string.Format(CultureInfo.InvariantCulture, "{0} {1}", this.Method, this.ResourcePath); } } /// @@ -93,7 +86,7 @@ public override string CommandIdentifier /// substituted for the tokens in the template. public Uri CreateCommandUri(Uri baseUri, Command commandToExecute) { - string[] urlParts = this.resourcePath.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries); + string[] urlParts = this.ResourcePath.Split(["/"], StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < urlParts.Length; i++) { string urlPart = urlParts[i]; @@ -103,13 +96,11 @@ public Uri CreateCommandUri(Uri baseUri, Command commandToExecute) } } - Uri fullUri; string relativeUrlString = string.Join("/", urlParts); Uri relativeUri = new Uri(relativeUrlString, UriKind.Relative); - bool uriCreateSucceeded = Uri.TryCreate(baseUri, relativeUri, out fullUri); - if (!uriCreateSucceeded) + if (!Uri.TryCreate(baseUri, relativeUri, out Uri? fullUri)) { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unable to create URI from base {0} and relative path {1}", baseUri == null ? string.Empty : baseUri.ToString(), relativeUrlString)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Unable to create URI from base {0} and relative path {1}", baseUri?.ToString(), relativeUrlString)); } return fullUri; @@ -133,11 +124,11 @@ private static string GetCommandPropertyValue(string propertyName, Command comma { // Extract the URL parameter, and remove it from the parameters dictionary // so it doesn't get transmitted as a JSON parameter. - if (commandToExecute.Parameters.ContainsKey(propertyName)) + if (commandToExecute.Parameters.TryGetValue(propertyName, out var propertyValueObject)) { - if (commandToExecute.Parameters[propertyName] != null) + if (propertyValueObject != null) { - propertyValue = commandToExecute.Parameters[propertyName].ToString(); + propertyValue = propertyValueObject.ToString()!; commandToExecute.Parameters.Remove(propertyName); } } diff --git a/dotnet/src/webdriver/ICommandExecutor.cs b/dotnet/src/webdriver/ICommandExecutor.cs index e80d7ea63b10e..6fef4d9bf3ef1 100644 --- a/dotnet/src/webdriver/ICommandExecutor.cs +++ b/dotnet/src/webdriver/ICommandExecutor.cs @@ -18,8 +18,11 @@ // using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +#nullable enable + namespace OpenQA.Selenium { /// @@ -31,15 +34,16 @@ public interface ICommandExecutor : IDisposable /// Attempts to add a command to the repository of commands known to this executor. /// /// The name of the command to attempt to add. - /// The describing the commnd to add. + /// The describing the command to add. /// if the new command has been added successfully; otherwise, . - bool TryAddCommand(string commandName, CommandInfo info); + bool TryAddCommand(string commandName, [NotNullWhen(true)] CommandInfo? info); /// /// Executes a command /// /// The command you wish to execute /// A response from the browser + /// If is . Response Execute(Command commandToExecute); @@ -48,6 +52,7 @@ public interface ICommandExecutor : IDisposable /// /// The command you wish to execute /// A task object representing the asynchronous operation + /// If is . Task ExecuteAsync(Command commandToExecute); } } diff --git a/dotnet/src/webdriver/ICustomDriverCommandExecutor.cs b/dotnet/src/webdriver/ICustomDriverCommandExecutor.cs index 07f2169490391..145bb8c66013d 100644 --- a/dotnet/src/webdriver/ICustomDriverCommandExecutor.cs +++ b/dotnet/src/webdriver/ICustomDriverCommandExecutor.cs @@ -18,6 +18,9 @@ // using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +#nullable enable namespace OpenQA.Selenium { @@ -32,7 +35,8 @@ public interface ICustomDriverCommandExecutor /// The name of the command to execute. The command name must be registered with the command executor, and must not be a command name known to this driver type. /// A containing the names and values of the parameters of the command. /// An object that contains the value returned by the command, if any. - object ExecuteCustomDriverCommand(string driverCommandToExecute, Dictionary parameters); + /// The command returned an exceptional value. + object? ExecuteCustomDriverCommand(string driverCommandToExecute, Dictionary parameters); /// /// Registers a set of commands to be executed with this driver instance. @@ -46,6 +50,6 @@ public interface ICustomDriverCommandExecutor /// The unique name of the command to register. /// The object describing the command. /// if the command was registered; otherwise, . - bool RegisterCustomDriverCommand(string commandName, CommandInfo commandInfo); + bool RegisterCustomDriverCommand(string commandName, [NotNullWhen(true)] CommandInfo? commandInfo); } } diff --git a/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs b/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs index b9bb4efcf0084..0f1d341c8c353 100644 --- a/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs +++ b/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs @@ -18,6 +18,7 @@ // using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; #nullable enable @@ -78,7 +79,7 @@ public DriverServiceCommandExecutor(DriverService service, HttpCommandExecutor c // get { return this.HttpExecutor.CommandInfoRepository; } //} - public bool TryAddCommand(string commandName, CommandInfo info) + public bool TryAddCommand(string commandName, [NotNullWhen(true)] CommandInfo? info) { return this.HttpExecutor.TryAddCommand(commandName, info); } @@ -94,6 +95,7 @@ public bool TryAddCommand(string commandName, CommandInfo info) /// /// The command you wish to execute /// A response from the browser + /// If is . public Response Execute(Command commandToExecute) { return Task.Run(() => this.ExecuteAsync(commandToExecute)).GetAwaiter().GetResult(); @@ -104,6 +106,7 @@ public Response Execute(Command commandToExecute) /// /// The command you wish to execute /// A task object representing the asynchronous operation + /// If is . public async Task ExecuteAsync(Command commandToExecute) { if (commandToExecute == null) diff --git a/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs b/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs index 9ab474b8ec235..2bcee9c7673ad 100644 --- a/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs +++ b/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs @@ -21,6 +21,7 @@ using OpenQA.Selenium.Internal.Logging; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Net; @@ -30,6 +31,8 @@ using System.Threading; using System.Threading.Tasks; +#nullable enable + namespace OpenQA.Selenium.Remote { /// @@ -47,7 +50,7 @@ public class HttpCommandExecutor : ICommandExecutor private readonly TimeSpan serverResponseTimeout; private bool isDisposed; private CommandInfoRepository commandInfoRepository = new W3CWireProtocolCommandInfoRepository(); - private HttpClient client; + private readonly Lazy client; private static readonly ILogger _logger = Log.GetLogger(); @@ -56,6 +59,7 @@ public class HttpCommandExecutor : ICommandExecutor /// /// Address of the WebDriver Server /// The timeout within which the server must respond. + /// If is . public HttpCommandExecutor(Uri addressOfRemoteServer, TimeSpan timeout) : this(addressOfRemoteServer, timeout, true) { @@ -85,20 +89,21 @@ public HttpCommandExecutor(Uri addressOfRemoteServer, TimeSpan timeout, bool ena this.remoteServerUri = addressOfRemoteServer; this.serverResponseTimeout = timeout; this.IsKeepAliveEnabled = enableKeepAlive; + this.client = new Lazy(CreateHttpClient); } /// /// Occurs when the is sending an HTTP /// request to the remote end WebDriver implementation. /// - public event EventHandler SendingRemoteHttpRequest; + public event EventHandler? SendingRemoteHttpRequest; /// /// Gets or sets an object to be used to proxy requests /// between this and the remote end WebDriver /// implementation. /// - public IWebProxy Proxy { get; set; } + public IWebProxy? Proxy { get; set; } /// /// Gets or sets a value indicating whether keep-alive is enabled for HTTP @@ -109,7 +114,7 @@ public HttpCommandExecutor(Uri addressOfRemoteServer, TimeSpan timeout, bool ena /// /// Gets or sets the user agent string used for HTTP communication - /// batween this and the remote end + /// between this and the remote end /// WebDriver implementation /// public string UserAgent { get; set; } @@ -128,9 +133,9 @@ protected CommandInfoRepository CommandInfoRepository /// Attempts to add a command to the repository of commands known to this executor. /// /// The name of the command to attempt to add. - /// The describing the commnd to add. + /// The describing the command to add. /// if the new command has been added successfully; otherwise, . - public bool TryAddCommand(string commandName, CommandInfo info) + public bool TryAddCommand(string commandName, [NotNullWhen(true)] CommandInfo? info) { if (info is not HttpCommandInfo commandInfo) { @@ -145,6 +150,7 @@ public bool TryAddCommand(string commandName, CommandInfo info) /// /// The command you wish to execute. /// A response from the browser. + /// If is . public virtual Response Execute(Command commandToExecute) { return Task.Run(() => this.ExecuteAsync(commandToExecute)).GetAwaiter().GetResult(); @@ -155,6 +161,7 @@ public virtual Response Execute(Command commandToExecute) /// /// The command you wish to execute. /// A task object representing the asynchronous operation. + /// If is . public virtual async Task ExecuteAsync(Command commandToExecute) { if (commandToExecute == null) @@ -164,20 +171,15 @@ public virtual async Task ExecuteAsync(Command commandToExecute) if (_logger.IsEnabled(LogEventLevel.Debug)) { - _logger.Debug($"Executing command: {commandToExecute}"); + _logger.Debug($"Executing command: [{commandToExecute.SessionId}]: {commandToExecute.Name}"); } - HttpCommandInfo info = this.commandInfoRepository.GetCommandInfo(commandToExecute.Name); + HttpCommandInfo? info = this.commandInfoRepository.GetCommandInfo(commandToExecute.Name); if (info == null) { throw new NotImplementedException(string.Format("The command you are attempting to execute, {0}, does not exist in the protocol dialect used by the remote end.", commandToExecute.Name)); } - if (this.client == null) - { - this.CreateHttpClient(); - } - HttpRequestInfo requestInfo = new HttpRequestInfo(this.remoteServerUri, commandToExecute, info); HttpResponseInfo responseInfo; try @@ -216,13 +218,10 @@ protected virtual void OnSendingRemoteHttpRequest(SendingRemoteHttpRequestEventA throw new ArgumentNullException(nameof(eventArgs), "eventArgs must not be null"); } - if (this.SendingRemoteHttpRequest != null) - { - this.SendingRemoteHttpRequest(this, eventArgs); - } + this.SendingRemoteHttpRequest?.Invoke(this, eventArgs); } - private void CreateHttpClient() + private HttpClient CreateHttpClient() { HttpClientHandler httpClientHandler = new HttpClientHandler(); string userInfo = this.remoteServerUri.UserInfo; @@ -242,16 +241,17 @@ private void CreateHttpClient() handler = new DiagnosticsHttpHandler(httpClientHandler, _logger); } - this.client = new HttpClient(handler); - this.client.DefaultRequestHeaders.UserAgent.ParseAdd(this.UserAgent); - this.client.DefaultRequestHeaders.Accept.ParseAdd(RequestAcceptHeader); - this.client.DefaultRequestHeaders.ExpectContinue = false; + var client = new HttpClient(handler); + client.DefaultRequestHeaders.UserAgent.ParseAdd(this.UserAgent); + client.DefaultRequestHeaders.Accept.ParseAdd(RequestAcceptHeader); + client.DefaultRequestHeaders.ExpectContinue = false; if (!this.IsKeepAliveEnabled) { - this.client.DefaultRequestHeaders.Connection.ParseAdd("close"); + client.DefaultRequestHeaders.Connection.ParseAdd("close"); } - this.client.Timeout = this.serverResponseTimeout; + client.Timeout = this.serverResponseTimeout; + return client; } private async Task MakeHttpRequest(HttpRequestInfo requestInfo) @@ -288,7 +288,7 @@ private async Task MakeHttpRequest(HttpRequestInfo requestInfo requestMessage.Content.Headers.ContentType = contentTypeHeader; } - using (HttpResponseMessage responseMessage = await this.client.SendAsync(requestMessage).ConfigureAwait(false)) + using (HttpResponseMessage responseMessage = await this.client.Value.SendAsync(requestMessage).ConfigureAwait(false)) { var responseBody = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); var responseContentType = responseMessage.Content.Headers.ContentType?.ToString(); @@ -332,8 +332,6 @@ private Response CreateResponse(HttpResponseInfo responseInfo) return response; } -#nullable enable - /// /// Releases all resources used by the . /// @@ -353,7 +351,10 @@ protected virtual void Dispose(bool disposing) { if (!this.isDisposed) { - this.client?.Dispose(); + if (this.client.IsValueCreated) + { + this.client.Value.Dispose(); + } this.isDisposed = true; } diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs index 974cbf608fcba..b5e8d5b6437b5 100644 --- a/dotnet/src/webdriver/WebDriver.cs +++ b/dotnet/src/webdriver/WebDriver.cs @@ -463,13 +463,16 @@ public INavigation Navigate() return new Navigator(this); } +#nullable enable + /// /// Executes a command with this driver. /// /// The name of the command to execute. The command name must be registered with the command executor, and must not be a command name known to this driver type. /// A containing the names and values of the parameters of the command. /// A containing information about the success or failure of the command and any data returned by the command. - public object ExecuteCustomDriverCommand(string driverCommandToExecute, Dictionary parameters) + /// The command returned an exceptional value. + public object? ExecuteCustomDriverCommand(string driverCommandToExecute, Dictionary parameters) { if (this.registeredCommands.Contains(driverCommandToExecute)) { @@ -497,7 +500,7 @@ public void RegisterCustomDriverCommands(IReadOnlyDictionaryThe unique name of the command to register. /// The object describing the command. /// if the command was registered; otherwise, . - public bool RegisterCustomDriverCommand(string commandName, CommandInfo commandInfo) + public bool RegisterCustomDriverCommand(string commandName, [NotNullWhen(true)] CommandInfo? commandInfo) { return this.RegisterDriverCommand(commandName, commandInfo, false); } @@ -509,17 +512,23 @@ public bool RegisterCustomDriverCommand(string commandName, CommandInfo commandI /// The object describing the command. /// if the registered command is internal to the driver; otherwise . /// if the command was registered; otherwise, . - internal bool RegisterDriverCommand(string commandName, CommandInfo commandInfo, bool isInternalCommand) + internal bool RegisterDriverCommand(string commandName, [NotNullWhen(true)] CommandInfo? commandInfo, bool isInternalCommand) { - bool commandAdded = this.CommandExecutor.TryAddCommand(commandName, commandInfo); - if (commandAdded && isInternalCommand) + if (this.CommandExecutor.TryAddCommand(commandName, commandInfo)) { - this.registeredCommands.Add(commandName); + if (isInternalCommand) + { + this.registeredCommands.Add(commandName); + } + + return true; } - return commandAdded; + return false; } +#nullable restore + /// /// Find the element in the response /// @@ -692,17 +701,21 @@ protected virtual Dictionary GetCapabilitiesDictionary(ICapabili return capabilitiesDictionary; } +#nullable enable + /// /// Registers a command to be executed with this driver instance as an internally known driver command. /// /// The unique name of the command to register. /// The object describing the command. /// if the command was registered; otherwise, . - protected bool RegisterInternalDriverCommand(string commandName, CommandInfo commandInfo) + protected bool RegisterInternalDriverCommand(string commandName, [NotNullWhen(true)] CommandInfo? commandInfo) { return this.RegisterDriverCommand(commandName, commandInfo, true); } +#nullable restore + /// /// Stops the client from running ///