Skip to content
175 changes: 46 additions & 129 deletions dotnet/src/webdriver/BiDi/BiDi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,164 +18,81 @@
// </copyright>

using System;
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using OpenQA.Selenium.BiDi.Communication;
using OpenQA.Selenium.BiDi.Communication.Json;
using OpenQA.Selenium.BiDi.Communication.Json.Converters;

namespace OpenQA.Selenium.BiDi;

public sealed class BiDi : IAsyncDisposable
{
private readonly Broker _broker;
private readonly JsonSerializerOptions _jsonOptions;
private readonly BiDiJsonSerializerContext _jsonContext;

private Session.SessionModule? _sessionModule;
private BrowsingContext.BrowsingContextModule? _browsingContextModule;
private Browser.BrowserModule? _browserModule;
private Network.NetworkModule? _networkModule;
private Input.InputModule? _inputModule;
private Script.ScriptModule? _scriptModule;
private Log.LogModule? _logModule;
private Storage.StorageModule? _storageModule;
private WebExtension.WebExtensionModule? _webExtensionModule;
private Emulation.EmulationModule? _emulationModule;

private readonly object _moduleLock = new();
private readonly ConcurrentDictionary<Type, Module> _modules = [];

private BiDi(string url)
{
var uri = new Uri(url);

_broker = new Broker(this, uri);
}

internal Session.SessionModule SessionModule
{
get
_jsonOptions = new JsonSerializerOptions
{
if (_sessionModule is not null) return _sessionModule;
lock (_moduleLock)
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,

// BiDi returns special numbers such as "NaN" as strings
// Additionally, -0 is returned as a string "-0"
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString,
Converters =
{
_sessionModule ??= new Session.SessionModule(_broker);
new BrowsingContextConverter(this),
new BrowserUserContextConverter(this),
new CollectorConverter(this),
new InterceptConverter(this),
new HandleConverter(this),
new InternalIdConverter(this),
new PreloadScriptConverter(this),
new RealmConverter(this),
new DateTimeOffsetConverter(),
new WebExtensionConverter(this),
}
return _sessionModule;
}
}
};

public BrowsingContext.BrowsingContextModule BrowsingContext
{
get
{
if (_browsingContextModule is not null) return _browsingContextModule;
lock (_moduleLock)
{
_browsingContextModule ??= new BrowsingContext.BrowsingContextModule(_broker);
}
return _browsingContextModule;
}
}
_jsonContext = new BiDiJsonSerializerContext(_jsonOptions);

public Browser.BrowserModule Browser
{
get
{
if (_browserModule is not null) return _browserModule;
lock (_moduleLock)
{
_browserModule ??= new Browser.BrowserModule(_broker);
}
return _browserModule;
}
_broker = new Broker(this, uri, _jsonOptions);
}

public Network.NetworkModule Network
{
get
{
if (_networkModule is not null) return _networkModule;
lock (_moduleLock)
{
_networkModule ??= new Network.NetworkModule(_broker);
}
return _networkModule;
}
}
internal Session.SessionModule SessionModule => AsModule<Session.SessionModule>();

internal Input.InputModule InputModule
{
get
{
if (_inputModule is not null) return _inputModule;
lock (_moduleLock)
{
_inputModule ??= new Input.InputModule(_broker);
}
return _inputModule;
}
}
public BrowsingContext.BrowsingContextModule BrowsingContext => AsModule<BrowsingContext.BrowsingContextModule>();

public Script.ScriptModule Script
{
get
{
if (_scriptModule is not null) return _scriptModule;
lock (_moduleLock)
{
_scriptModule ??= new Script.ScriptModule(_broker);
}
return _scriptModule;
}
}
public Browser.BrowserModule Browser => AsModule<Browser.BrowserModule>();

public Log.LogModule Log
{
get
{
if (_logModule is not null) return _logModule;
lock (_moduleLock)
{
_logModule ??= new Log.LogModule(_broker);
}
return _logModule;
}
}
public Network.NetworkModule Network => AsModule<Network.NetworkModule>();

public Storage.StorageModule Storage
{
get
{
if (_storageModule is not null) return _storageModule;
lock (_moduleLock)
{
_storageModule ??= new Storage.StorageModule(_broker);
}
return _storageModule;
}
}
internal Input.InputModule InputModule => AsModule<Input.InputModule>();

public WebExtension.WebExtensionModule WebExtension
{
get
{
if (_webExtensionModule is not null) return _webExtensionModule;
lock (_moduleLock)
{
_webExtensionModule ??= new WebExtension.WebExtensionModule(_broker);
}
return _webExtensionModule;
}
}
public Script.ScriptModule Script => AsModule<Script.ScriptModule>();

public Log.LogModule Log => AsModule<Log.LogModule>();

public Emulation.EmulationModule Emulation
public Storage.StorageModule Storage => AsModule<Storage.StorageModule>();

public WebExtension.WebExtensionModule WebExtension => AsModule<WebExtension.WebExtensionModule>();

public Emulation.EmulationModule Emulation => AsModule<Emulation.EmulationModule>();

public TModule AsModule<TModule>() where TModule : Module, new()
{
get
{
if (_emulationModule is not null) return _emulationModule;
lock (_moduleLock)
{
_emulationModule ??= new Emulation.EmulationModule(_broker);
}
return _emulationModule;
}
return (TModule)_modules.GetOrAdd(typeof(TModule), _ => Module.Create<TModule>(this, _broker, _jsonOptions, _jsonContext));
}

public Task<Session.StatusResult> StatusAsync()
Expand Down
20 changes: 10 additions & 10 deletions dotnet/src/webdriver/BiDi/Browser/BrowserModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,60 +17,60 @@
// under the License.
// </copyright>

using System.Threading.Tasks;
using OpenQA.Selenium.BiDi.Communication;
using System.Threading.Tasks;

namespace OpenQA.Selenium.BiDi.Browser;

public sealed class BrowserModule(Broker broker) : Module(broker)
public sealed class BrowserModule : Module
{
public async Task<EmptyResult> CloseAsync(CloseOptions? options = null)
{
return await Broker.ExecuteCommandAsync<CloseCommand, EmptyResult>(new CloseCommand(), options).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync<CloseCommand, EmptyResult>(new CloseCommand(), options, JsonContext).ConfigureAwait(false);
}

public async Task<UserContextInfo> CreateUserContextAsync(CreateUserContextOptions? options = null)
{
var @params = new CreateUserContextParameters(options?.AcceptInsecureCerts, options?.Proxy, options?.UnhandledPromptBehavior);

return await Broker.ExecuteCommandAsync<CreateUserContextCommand, UserContextInfo>(new CreateUserContextCommand(@params), options).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync<CreateUserContextCommand, UserContextInfo>(new CreateUserContextCommand(@params), options, JsonContext).ConfigureAwait(false);
}

public async Task<GetUserContextsResult> GetUserContextsAsync(GetUserContextsOptions? options = null)
{
return await Broker.ExecuteCommandAsync<GetUserContextsCommand, GetUserContextsResult>(new GetUserContextsCommand(), options).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync<GetUserContextsCommand, GetUserContextsResult>(new GetUserContextsCommand(), options, JsonContext).ConfigureAwait(false);
}

public async Task<EmptyResult> RemoveUserContextAsync(UserContext userContext, RemoveUserContextOptions? options = null)
{
var @params = new RemoveUserContextParameters(userContext);

return await Broker.ExecuteCommandAsync<RemoveUserContextCommand, EmptyResult>(new RemoveUserContextCommand(@params), options).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync<RemoveUserContextCommand, EmptyResult>(new RemoveUserContextCommand(@params), options, JsonContext).ConfigureAwait(false);
}

public async Task<GetClientWindowsResult> GetClientWindowsAsync(GetClientWindowsOptions? options = null)
{
return await Broker.ExecuteCommandAsync<GetClientWindowsCommand, GetClientWindowsResult>(new(), options).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync<GetClientWindowsCommand, GetClientWindowsResult>(new(), options, JsonContext).ConfigureAwait(false);
}

public async Task<EmptyResult> SetDownloadBehaviorAllowedAsync(string destinationFolder, SetDownloadBehaviorOptions? options = null)
{
var @params = new SetDownloadBehaviorParameters(new DownloadBehaviorAllowed(destinationFolder), options?.UserContexts);

return await Broker.ExecuteCommandAsync<SetDownloadBehaviorCommand, EmptyResult>(new SetDownloadBehaviorCommand(@params), options).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync<SetDownloadBehaviorCommand, EmptyResult>(new SetDownloadBehaviorCommand(@params), options, JsonContext).ConfigureAwait(false);
}

public async Task<EmptyResult> SetDownloadBehaviorAllowedAsync(SetDownloadBehaviorOptions? options = null)
{
var @params = new SetDownloadBehaviorParameters(null, options?.UserContexts);

return await Broker.ExecuteCommandAsync<SetDownloadBehaviorCommand, EmptyResult>(new SetDownloadBehaviorCommand(@params), options).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync<SetDownloadBehaviorCommand, EmptyResult>(new SetDownloadBehaviorCommand(@params), options, JsonContext).ConfigureAwait(false);
}

public async Task<EmptyResult> SetDownloadBehaviorDeniedAsync(SetDownloadBehaviorOptions? options = null)
{
var @params = new SetDownloadBehaviorParameters(new DownloadBehaviorDenied(), options?.UserContexts);

return await Broker.ExecuteCommandAsync<SetDownloadBehaviorCommand, EmptyResult>(new SetDownloadBehaviorCommand(@params), options).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync<SetDownloadBehaviorCommand, EmptyResult>(new SetDownloadBehaviorCommand(@params), options, JsonContext).ConfigureAwait(false);
}
}
Loading