Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b595dfc
[dotnet] Enable external BiDi modules
RenderMichael Oct 11, 2025
eaa3d5c
Add InternalModule
RenderMichael Oct 11, 2025
71a7489
Rename InternalModule to CoreModule
RenderMichael Oct 11, 2025
1fbf4c6
[dotnet] Implement Permissions module
RenderMichael Oct 11, 2025
9a26d1e
Rename type
RenderMichael Oct 11, 2025
dbe8e0b
Remove CoreModule
RenderMichael Oct 11, 2025
6449944
Move Permissions module out of a separate Extensions folder
RenderMichael Oct 20, 2025
43dd406
Remove unused usings
RenderMichael Oct 20, 2025
341338f
Rename Initialize to CreateJsonContext
RenderMichael Oct 20, 2025
8d3b1b0
Merge branch 'trunk' into enable-external-bidi-modules
RenderMichael Oct 20, 2025
6bec772
Fix format
RenderMichael Oct 20, 2025
875f5e4
Rename extensions files, move Permissions into a separate file from g…
RenderMichael Oct 22, 2025
3c6d2f7
Rename PermissionsExtensions to PermissionsBiDiExtensions
RenderMichael Oct 24, 2025
f958863
merge trunk
RenderMichael Nov 29, 2025
22e3caa
Fix imports after Communications refactor
RenderMichael Nov 29, 2025
cb6e3f2
[dotnet] Add missing embeddedOrigin parameter, PR feedback
RenderMichael Nov 29, 2025
194e2c8
[dotnet] Expose PermissionDescriptor directly
RenderMichael Nov 29, 2025
15f9ee5
Merge remote-tracking branch 'upstream/trunk' into pr/16414
nvborisenko Nov 29, 2025
6a010eb
Fix merge
nvborisenko Nov 29, 2025
c72b422
Merge branch 'enable-external-bidi-modules' of https://github.com/Ren…
nvborisenko Nov 29, 2025
e10ef6c
Format
nvborisenko Nov 29, 2025
dd903dc
Format globally
nvborisenko Nov 29, 2025
bf05704
Unify command signatures
nvborisenko Nov 29, 2025
b3fd21f
Fix test
nvborisenko Nov 29, 2025
15e0d1a
Rename descriptor
nvborisenko Nov 29, 2025
080749c
JsonContext per module
nvborisenko Nov 29, 2025
3108646
Merge remote-tracking branch 'upstream/trunk' into bidi-per-module-js…
nvborisenko Nov 29, 2025
217d859
Hide GetJsonOptions internally
nvborisenko Nov 29, 2025
8e140a9
Cache modules
nvborisenko Nov 29, 2025
d4b5310
Module may have access to BiDi
nvborisenko Nov 29, 2025
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
94 changes: 48 additions & 46 deletions dotnet/src/webdriver/BiDi/BiDi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
// under the License.
// </copyright>

using OpenQA.Selenium.BiDi.Json;
using OpenQA.Selenium.BiDi.Json.Converters;
using System;
using System.Collections.Concurrent;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
Expand All @@ -29,56 +29,24 @@ namespace OpenQA.Selenium.BiDi;

public sealed class BiDi : IAsyncDisposable
{
internal Broker Broker { get; }
internal JsonSerializerOptions JsonOptions { get; }
private readonly BiDiJsonSerializerContext _jsonContext;

public JsonSerializerOptions DefaultBiDiOptions()
{
return new JsonSerializerOptions
{
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 =
{
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),
}
};
}
private readonly ConcurrentDictionary<Type, Module> _modules = new();

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

JsonOptions = DefaultBiDiOptions();

_jsonContext = new BiDiJsonSerializerContext(JsonOptions);

Broker = new Broker(this, uri, JsonOptions);
SessionModule = Module.Create<Session.SessionModule>(this, JsonOptions, _jsonContext);
BrowsingContext = Module.Create<BrowsingContext.BrowsingContextModule>(this, JsonOptions, _jsonContext);
Browser = Module.Create<Browser.BrowserModule>(this, JsonOptions, _jsonContext);
Network = Module.Create<Network.NetworkModule>(this, JsonOptions, _jsonContext);
InputModule = Module.Create<Input.InputModule>(this, JsonOptions, _jsonContext);
Script = Module.Create<Script.ScriptModule>(this, JsonOptions, _jsonContext);
Log = Module.Create<Log.LogModule>(this, JsonOptions, _jsonContext);
Storage = Module.Create<Storage.StorageModule>(this, JsonOptions, _jsonContext);
WebExtension = Module.Create<WebExtension.WebExtensionModule>(this, JsonOptions, _jsonContext);
Emulation = Module.Create<Emulation.EmulationModule>(this, JsonOptions, _jsonContext);
Broker = new Broker(this, uri);

SessionModule = AsModule<Session.SessionModule>();
BrowsingContext = AsModule<BrowsingContext.BrowsingContextModule>();
Browser = AsModule<Browser.BrowserModule>();
Network = AsModule<Network.NetworkModule>();
InputModule = AsModule<Input.InputModule>();
Script = AsModule<Script.ScriptModule>();
Log = AsModule<Log.LogModule>();
Storage = AsModule<Storage.StorageModule>();
WebExtension = AsModule<WebExtension.WebExtensionModule>();
Emulation = AsModule<Emulation.EmulationModule>();
}

internal Session.SessionModule SessionModule { get; }
Expand Down Expand Up @@ -125,4 +93,38 @@ public async ValueTask DisposeAsync()
await Broker.DisposeAsync().ConfigureAwait(false);
GC.SuppressFinalize(this);
}

public T AsModule<T>() where T : Module, new()
{
return (T)_modules.GetOrAdd(typeof(T), Module.Create<T>(this, Broker, GetJsonOptions()));
}

private Broker Broker { get; }

private JsonSerializerOptions GetJsonOptions()
{
return new JsonSerializerOptions
{
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 =
{
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),
}
};
}
}
2 changes: 1 addition & 1 deletion dotnet/src/webdriver/BiDi/Broker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public sealed class Broker : IAsyncDisposable
private Task? _eventEmitterTask;
private CancellationTokenSource? _receiveMessagesCancellationTokenSource;

internal Broker(BiDi bidi, Uri url, JsonSerializerOptions jsonOptions)
internal Broker(BiDi bidi, Uri url)
{
_bidi = bidi;
_transport = new WebSocketTransport(url);
Expand Down
24 changes: 12 additions & 12 deletions dotnet/src/webdriver/BiDi/Browser/BrowserModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,67 +19,67 @@

using OpenQA.Selenium.BiDi.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace OpenQA.Selenium.BiDi.Browser;

public sealed class BrowserModule : Module
{
internal new BiDiJsonSerializerContext JsonContext => (BiDiJsonSerializerContext)base.JsonContext;
private BiDiJsonSerializerContext _jsonContext = null!;

public async Task<CloseResult> CloseAsync(CloseOptions? options = null)
{
return await Broker.ExecuteCommandAsync(new CloseCommand(), options, JsonContext.Browser_CloseCommand, JsonContext.Browser_CloseResult).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync(new CloseCommand(), options, _jsonContext.Browser_CloseCommand, _jsonContext.Browser_CloseResult).ConfigureAwait(false);
}

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

return await Broker.ExecuteCommandAsync(new CreateUserContextCommand(@params), options, JsonContext.CreateUserContextCommand, JsonContext.CreateUserContextResult).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync(new CreateUserContextCommand(@params), options, _jsonContext.CreateUserContextCommand, _jsonContext.CreateUserContextResult).ConfigureAwait(false);
}

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

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

return await Broker.ExecuteCommandAsync(new RemoveUserContextCommand(@params), options, JsonContext.RemoveUserContextCommand, JsonContext.RemoveUserContextResult).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync(new RemoveUserContextCommand(@params), options, _jsonContext.RemoveUserContextCommand, _jsonContext.RemoveUserContextResult).ConfigureAwait(false);
}

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

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

return await Broker.ExecuteCommandAsync(new SetDownloadBehaviorCommand(@params), options, JsonContext.SetDownloadBehaviorCommand, JsonContext.SetDownloadBehaviorResult).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync(new SetDownloadBehaviorCommand(@params), options, _jsonContext.SetDownloadBehaviorCommand, _jsonContext.SetDownloadBehaviorResult).ConfigureAwait(false);
}

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

return await Broker.ExecuteCommandAsync(new SetDownloadBehaviorCommand(@params), options, JsonContext.SetDownloadBehaviorCommand, JsonContext.SetDownloadBehaviorResult).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync(new SetDownloadBehaviorCommand(@params), options, _jsonContext.SetDownloadBehaviorCommand, _jsonContext.SetDownloadBehaviorResult).ConfigureAwait(false);
}

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

return await Broker.ExecuteCommandAsync(new SetDownloadBehaviorCommand(@params), options, JsonContext.SetDownloadBehaviorCommand, JsonContext.SetDownloadBehaviorResult).ConfigureAwait(false);
return await Broker.ExecuteCommandAsync(new SetDownloadBehaviorCommand(@params), options, _jsonContext.SetDownloadBehaviorCommand, _jsonContext.SetDownloadBehaviorResult).ConfigureAwait(false);
}
protected override JsonSerializerContext CreateJsonContext(JsonSerializerOptions options)

protected override void Initialize(JsonSerializerOptions options)
{
return new BiDiJsonSerializerContext(options);
_jsonContext = new BiDiJsonSerializerContext(options);
}
}
Loading