Skip to content

Commit 7217bf6

Browse files
committed
Implement BiDi extensions, add Permissions extension
1 parent 7e0efd4 commit 7217bf6

35 files changed

+451
-127
lines changed

dotnet/src/webdriver/BiDi/BiDi.cs

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@
1717
// under the License.
1818
// </copyright>
1919

20+
using OpenQA.Selenium.BiDi.Communication;
21+
using OpenQA.Selenium.BiDi.Communication.Json;
22+
using OpenQA.Selenium.BiDi.Communication.Json.Converters;
2023
using System;
2124
using System.Threading.Tasks;
22-
using OpenQA.Selenium.BiDi.Communication;
2325

2426
namespace OpenQA.Selenium.BiDi;
2527

2628
public class BiDi : IAsyncDisposable
2729
{
28-
private readonly Broker _broker;
30+
private readonly bool _ownsConnection;
31+
protected BiDiConnection BiDiConnection { get; }
2932

30-
private readonly Lazy<Modules.Session.SessionModule> _sessionModule;
3133
private readonly Lazy<Modules.BrowsingContext.BrowsingContextModule> _browsingContextModule;
3234
private readonly Lazy<Modules.Browser.BrowserModule> _browserModule;
3335
private readonly Lazy<Modules.Network.NetworkModule> _networkModule;
@@ -36,23 +38,49 @@ public class BiDi : IAsyncDisposable
3638
private readonly Lazy<Modules.Log.LogModule> _logModule;
3739
private readonly Lazy<Modules.Storage.StorageModule> _storageModule;
3840

39-
internal BiDi(string url)
41+
private BiDi(BiDiConnection connection) : this()
42+
{
43+
_ownsConnection = false;
44+
BiDiConnection = connection;
45+
AddBiDiModuleJsonInfo(connection);
46+
}
47+
48+
private BiDi(string url) : this()
49+
{
50+
_ownsConnection = true;
51+
BiDiConnection = new BiDiConnection(new Uri(url));
52+
AddBiDiModuleJsonInfo(BiDiConnection);
53+
}
54+
55+
private BiDi()
4056
{
41-
var uri = new Uri(url);
42-
43-
_broker = new Broker(this, uri);
44-
45-
_sessionModule = new Lazy<Modules.Session.SessionModule>(() => new Modules.Session.SessionModule(_broker));
46-
_browsingContextModule = new Lazy<Modules.BrowsingContext.BrowsingContextModule>(() => new Modules.BrowsingContext.BrowsingContextModule(_broker));
47-
_browserModule = new Lazy<Modules.Browser.BrowserModule>(() => new Modules.Browser.BrowserModule(_broker));
48-
_networkModule = new Lazy<Modules.Network.NetworkModule>(() => new Modules.Network.NetworkModule(_broker));
49-
_inputModule = new Lazy<Modules.Input.InputModule>(() => new Modules.Input.InputModule(_broker));
50-
_scriptModule = new Lazy<Modules.Script.ScriptModule>(() => new Modules.Script.ScriptModule(_broker));
51-
_logModule = new Lazy<Modules.Log.LogModule>(() => new Modules.Log.LogModule(_broker));
52-
_storageModule = new Lazy<Modules.Storage.StorageModule>(() => new Modules.Storage.StorageModule(_broker));
57+
_browsingContextModule = new Lazy<Modules.BrowsingContext.BrowsingContextModule>(() => new Modules.BrowsingContext.BrowsingContextModule(BiDiConnection));
58+
_browserModule = new Lazy<Modules.Browser.BrowserModule>(() => new Modules.Browser.BrowserModule(BiDiConnection));
59+
_networkModule = new Lazy<Modules.Network.NetworkModule>(() => new Modules.Network.NetworkModule(BiDiConnection));
60+
_inputModule = new Lazy<Modules.Input.InputModule>(() => new Modules.Input.InputModule(BiDiConnection));
61+
_scriptModule = new Lazy<Modules.Script.ScriptModule>(() => new Modules.Script.ScriptModule(BiDiConnection));
62+
_logModule = new Lazy<Modules.Log.LogModule>(() => new Modules.Log.LogModule(BiDiConnection));
63+
_storageModule = new Lazy<Modules.Storage.StorageModule>(() => new Modules.Storage.StorageModule(BiDiConnection));
5364
}
5465

55-
internal Modules.Session.SessionModule SessionModule => _sessionModule.Value;
66+
private BiDiConnection AddBiDiModuleJsonInfo(BiDiConnection connection)
67+
{
68+
connection.AddSerializerContextAndConverters(BiDiJsonSerializerContext.Default,
69+
[
70+
new BrowsingContextConverter(this),
71+
new BrowserUserContextConverter(this),
72+
new InterceptConverter(this),
73+
new RequestConverter(this),
74+
new HandleConverter(this),
75+
new InternalIdConverter(this),
76+
new PreloadScriptConverter(this),
77+
new RealmConverter(this),
78+
]);
79+
80+
return connection;
81+
}
82+
83+
internal Modules.Session.SessionModule SessionModule => BiDiConnection.SessionModule;
5684
public Modules.BrowsingContext.BrowsingContextModule BrowsingContext => _browsingContextModule.Value;
5785
public Modules.Browser.BrowserModule Browser => _browserModule.Value;
5886
public Modules.Network.NetworkModule Network => _networkModule.Value;
@@ -70,11 +98,18 @@ public static async Task<BiDi> ConnectAsync(string url)
7098
{
7199
var bidi = new BiDi(url);
72100

73-
await bidi._broker.ConnectAsync(default).ConfigureAwait(false);
101+
await bidi.BiDiConnection.ConnectAsync(default).ConfigureAwait(false);
74102

75103
return bidi;
76104
}
77105

106+
public static ValueTask<BiDi> AttachAsync(BiDiConnection connection)
107+
{
108+
var bidi = new BiDi(connection);
109+
110+
return new ValueTask<BiDi>(bidi);
111+
}
112+
78113
public Task EndAsync(Modules.Session.EndOptions? options = null)
79114
{
80115
return SessionModule.EndAsync(options);
@@ -88,6 +123,9 @@ public async ValueTask DisposeAsync()
88123

89124
protected virtual async ValueTask DisposeAsyncCore()
90125
{
91-
await _broker.DisposeAsync().ConfigureAwait(false);
126+
if (_ownsConnection)
127+
{
128+
await BiDiConnection.DisposeAsync().ConfigureAwait(false);
129+
}
92130
}
93131
}

dotnet/src/webdriver/BiDi/Communication/Broker.cs renamed to dotnet/src/webdriver/BiDi/Communication/BiDiConnection.cs

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <copyright file="Broker.cs" company="Selenium Committers">
1+
// <copyright file="BiDiConnection.cs" company="Selenium Committers">
22
// Licensed to the Software Freedom Conservancy (SFC) under one
33
// or more contributor license agreements. See the NOTICE file
44
// distributed with this work for additional information
@@ -18,25 +18,24 @@
1818
// </copyright>
1919

2020
using OpenQA.Selenium.BiDi.Communication.Json;
21-
using OpenQA.Selenium.BiDi.Communication.Json.Converters;
2221
using OpenQA.Selenium.BiDi.Communication.Transport;
2322
using OpenQA.Selenium.Internal.Logging;
2423
using System;
2524
using System.Collections.Concurrent;
2625
using System.Collections.Generic;
26+
using System.Diagnostics.CodeAnalysis;
2727
using System.Linq;
2828
using System.Text.Json;
2929
using System.Text.Json.Serialization;
30+
using System.Text.Json.Serialization.Metadata;
3031
using System.Threading;
3132
using System.Threading.Tasks;
3233

3334
namespace OpenQA.Selenium.BiDi.Communication;
3435

35-
public class Broker : IAsyncDisposable
36+
public class BiDiConnection : IAsyncDisposable
3637
{
37-
private readonly ILogger _logger = Log.GetLogger<Broker>();
38-
39-
private readonly BiDi _bidi;
38+
private readonly ILogger _logger = Log.GetLogger<BiDiConnection>();
4039
private readonly ITransport _transport;
4140

4241
private readonly ConcurrentDictionary<int, TaskCompletionSource<JsonElement>> _pendingCommands = new();
@@ -52,39 +51,42 @@ public class Broker : IAsyncDisposable
5251
private Task? _eventEmitterTask;
5352
private CancellationTokenSource? _receiveMessagesCancellationTokenSource;
5453

55-
private readonly BiDiJsonSerializerContext _jsonSerializerContext;
54+
private readonly JsonSerializerOptions _jsonSerializerContext;
55+
private readonly Lazy<Modules.Session.SessionModule> _sessionModule;
56+
57+
internal Modules.Session.SessionModule SessionModule => _sessionModule.Value;
5658

57-
internal Broker(BiDi bidi, Uri url)
59+
public BiDiConnection(Uri url)
5860
{
59-
_bidi = bidi;
6061
_transport = new WebSocketTransport(url);
62+
_jsonSerializerContext = BiDiConnectionJsonSerializerContext.CreateOptions();
63+
_sessionModule = new Lazy<Modules.Session.SessionModule>(() => new Modules.Session.SessionModule(this));
64+
}
6165

62-
var jsonSerializerOptions = new JsonSerializerOptions
66+
[RequiresUnreferencedCode("Enables reflection-based JSON serialization. Use a source-generated JsonSerializerContext for AOT safety.")]
67+
[RequiresDynamicCode("Enables reflection-based JSON serialization. Use a source-generated JsonSerializerContext for AOT safety.")]
68+
public void EnableReflectionBasedJson()
69+
{
70+
if (_jsonSerializerContext.IsReadOnly)
6371
{
64-
PropertyNameCaseInsensitive = true,
65-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
66-
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
67-
68-
// BiDi returns special numbers such as "NaN" as strings
69-
// Additionally, -0 is returned as a string "-0"
70-
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString,
71-
Converters =
72-
{
73-
new BrowsingContextConverter(_bidi),
74-
new BrowserUserContextConverter(_bidi),
75-
new InterceptConverter(_bidi),
76-
new RequestConverter(_bidi),
77-
new HandleConverter(_bidi),
78-
new InternalIdConverter(_bidi),
79-
new PreloadScriptConverter(_bidi),
80-
new RealmConverter(_bidi),
81-
82-
new BiDiDateTimeOffsetConverter(),
83-
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase),
84-
}
85-
};
72+
throw new InvalidOperationException("Cannot add JSON serializer context after ConnectAsync has been called");
73+
}
74+
75+
_jsonSerializerContext.TypeInfoResolverChain.Add(new DefaultJsonTypeInfoResolver());
76+
}
77+
78+
public void AddSerializerContextAndConverters(JsonSerializerContext context, IList<JsonConverter>? converters = null)
79+
{
80+
if (_jsonSerializerContext.IsReadOnly)
81+
{
82+
throw new InvalidOperationException("Cannot add JSON serializer context after ConnectAsync has been called");
83+
}
8684

87-
_jsonSerializerContext = new BiDiJsonSerializerContext(jsonSerializerOptions);
85+
_jsonSerializerContext.TypeInfoResolverChain.Add(context);
86+
foreach (JsonConverter converter in converters ?? [])
87+
{
88+
_jsonSerializerContext.Converters.Add(converter);
89+
}
8890
}
8991

9092
public async Task ConnectAsync(CancellationToken cancellationToken)
@@ -102,20 +104,31 @@ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
102104
{
103105
var data = await _transport.ReceiveAsync(cancellationToken).ConfigureAwait(false);
104106

105-
var message = JsonSerializer.Deserialize(new ReadOnlySpan<byte>(data), _jsonSerializerContext.Message);
107+
var messageTypeInfo = (JsonTypeInfo<Message>)_jsonSerializerContext.GetTypeInfo(typeof(Message));
108+
var message = JsonSerializer.Deserialize(new ReadOnlySpan<byte>(data), messageTypeInfo);
106109

107110
switch (message)
108111
{
109112
case MessageSuccess messageSuccess:
110113
_pendingCommands[messageSuccess.Id].SetResult(messageSuccess.Result);
111114
_pendingCommands.TryRemove(messageSuccess.Id, out _);
112115
break;
116+
113117
case MessageEvent messageEvent:
114118
_pendingEvents.Add(messageEvent);
115119
break;
116-
case MessageError mesageError:
117-
_pendingCommands[mesageError.Id].SetException(new BiDiException($"{mesageError.Error}: {mesageError.Message}"));
118-
_pendingCommands.TryRemove(mesageError.Id, out _);
120+
121+
case MessageError messageError:
122+
_pendingCommands[messageError.Id].SetException(new BiDiException($"{messageError.Error}: {messageError.Message}"));
123+
_pendingCommands.TryRemove(messageError.Id, out _);
124+
break;
125+
126+
default:
127+
if (_logger.IsEnabled(LogEventLevel.Warn))
128+
{
129+
_logger.Warn($"Received invalid message type: {message}");
130+
}
131+
119132
break;
120133
}
121134
}
@@ -135,7 +148,7 @@ private async Task ProcessEventsAwaiterAsync()
135148
{
136149
var args = (EventArgs)result.Params.Deserialize(handler.EventArgsType, _jsonSerializerContext)!;
137150

138-
args.BiDi = _bidi;
151+
args.BiDi = this;
139152

140153
// handle browsing context subscriber
141154
if (handler.Contexts is not null && args is BrowsingContextEventArgs browsingContextEventArgs && handler.Contexts.Contains(browsingContextEventArgs.Context))
@@ -204,7 +217,7 @@ public async Task<Subscription> SubscribeAsync<TEventArgs>(string eventName, Act
204217

205218
if (options is BrowsingContextsSubscriptionOptions browsingContextsOptions)
206219
{
207-
var subscribeResult = await _bidi.SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false);
220+
var subscribeResult = await SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false);
208221

209222
var eventHandler = new SyncEventHandler<TEventArgs>(eventName, action, browsingContextsOptions?.Contexts);
210223

@@ -214,7 +227,7 @@ public async Task<Subscription> SubscribeAsync<TEventArgs>(string eventName, Act
214227
}
215228
else
216229
{
217-
var subscribeResult = await _bidi.SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false);
230+
var subscribeResult = await SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false);
218231

219232
var eventHandler = new SyncEventHandler<TEventArgs>(eventName, action);
220233

@@ -231,7 +244,7 @@ public async Task<Subscription> SubscribeAsync<TEventArgs>(string eventName, Fun
231244

232245
if (options is BrowsingContextsSubscriptionOptions browsingContextsOptions)
233246
{
234-
var subscribeResult = await _bidi.SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false);
247+
var subscribeResult = await SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false);
235248

236249
var eventHandler = new AsyncEventHandler<TEventArgs>(eventName, func, browsingContextsOptions.Contexts);
237250

@@ -241,7 +254,7 @@ public async Task<Subscription> SubscribeAsync<TEventArgs>(string eventName, Fun
241254
}
242255
else
243256
{
244-
var subscribeResult = await _bidi.SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false);
257+
var subscribeResult = await SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false);
245258

246259
var eventHandler = new AsyncEventHandler<TEventArgs>(eventName, func);
247260

@@ -259,22 +272,22 @@ public async Task UnsubscribeAsync(Modules.Session.Subscription subscription, Ev
259272

260273
if (subscription is not null)
261274
{
262-
await _bidi.SessionModule.UnsubscribeAsync([subscription]).ConfigureAwait(false);
275+
await SessionModule.UnsubscribeAsync([subscription]).ConfigureAwait(false);
263276
}
264277
else
265278
{
266279
if (eventHandler.Contexts is not null)
267280
{
268281
if (!eventHandlers.Any(h => eventHandler.Contexts.Equals(h.Contexts)) && !eventHandlers.Any(h => h.Contexts is null))
269282
{
270-
await _bidi.SessionModule.UnsubscribeAsync([eventHandler.EventName], new() { Contexts = eventHandler.Contexts }).ConfigureAwait(false);
283+
await SessionModule.UnsubscribeAsync([eventHandler.EventName], new() { Contexts = eventHandler.Contexts }).ConfigureAwait(false);
271284
}
272285
}
273286
else
274287
{
275288
if (!eventHandlers.Any(h => h.Contexts is not null) && !eventHandlers.Any(h => h.Contexts is null))
276289
{
277-
await _bidi.SessionModule.UnsubscribeAsync([eventHandler.EventName]).ConfigureAwait(false);
290+
await SessionModule.UnsubscribeAsync([eventHandler.EventName]).ConfigureAwait(false);
278291
}
279292
}
280293
}

0 commit comments

Comments
 (0)