diff --git a/dotnet/src/webdriver/BiDi/Communication/Broker.cs b/dotnet/src/webdriver/BiDi/Communication/Broker.cs index 433f6a604a262..89ad49d49a243 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Broker.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Broker.cs @@ -83,6 +83,7 @@ internal Broker(BiDi bidi, ITransport transport) new DateTimeOffsetConverter(), new PrintPageRangeConverter(), new InputOriginConverter(), + new SubscriptionConverter(), new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), // https://github.com/dotnet/runtime/issues/72604 @@ -223,23 +224,23 @@ public async Task SubscribeAsync(string eventName, Act if (options is BrowsingContextsSubscriptionOptions browsingContextsOptions) { - await _bidi.SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false); + var subscribeResult = await _bidi.SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false); var eventHandler = new SyncEventHandler(eventName, action, browsingContextsOptions?.Contexts); handlers.Add(eventHandler); - return new Subscription(this, eventHandler); + return new Subscription(subscribeResult.Subscription, this, eventHandler); } else { - await _bidi.SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false); + var subscribeResult = await _bidi.SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false); var eventHandler = new SyncEventHandler(eventName, action); handlers.Add(eventHandler); - return new Subscription(this, eventHandler); + return new Subscription(subscribeResult.Subscription, this, eventHandler); } } @@ -250,44 +251,51 @@ public async Task SubscribeAsync(string eventName, Fun if (options is BrowsingContextsSubscriptionOptions browsingContextsOptions) { - await _bidi.SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false); + var subscribeResult = await _bidi.SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false); var eventHandler = new AsyncEventHandler(eventName, func, browsingContextsOptions.Contexts); handlers.Add(eventHandler); - return new Subscription(this, eventHandler); + return new Subscription(subscribeResult.Subscription, this, eventHandler); } else { - await _bidi.SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false); + var subscribeResult = await _bidi.SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false); var eventHandler = new AsyncEventHandler(eventName, func); handlers.Add(eventHandler); - return new Subscription(this, eventHandler); + return new Subscription(subscribeResult.Subscription, this, eventHandler); } } - public async Task UnsubscribeAsync(EventHandler eventHandler) + public async Task UnsubscribeAsync(Modules.Session.Subscription subscription, EventHandler eventHandler) { var eventHandlers = _eventHandlers[eventHandler.EventName]; eventHandlers.Remove(eventHandler); - if (eventHandler.Contexts is not null) + if (subscription is not null) { - if (!eventHandlers.Any(h => eventHandler.Contexts.Equals(h.Contexts)) && !eventHandlers.Any(h => h.Contexts is null)) - { - await _bidi.SessionModule.UnsubscribeAsync([eventHandler.EventName], new() { Contexts = eventHandler.Contexts }).ConfigureAwait(false); - } + await _bidi.SessionModule.UnsubscribeAsync([subscription]).ConfigureAwait(false); } else { - if (!eventHandlers.Any(h => h.Contexts is not null) && !eventHandlers.Any(h => h.Contexts is null)) + if (eventHandler.Contexts is not null) + { + if (!eventHandlers.Any(h => eventHandler.Contexts.Equals(h.Contexts)) && !eventHandlers.Any(h => h.Contexts is null)) + { + await _bidi.SessionModule.UnsubscribeAsync([eventHandler.EventName], new() { Contexts = eventHandler.Contexts }).ConfigureAwait(false); + } + } + else { - await _bidi.SessionModule.UnsubscribeAsync([eventHandler.EventName]).ConfigureAwait(false); + if (!eventHandlers.Any(h => h.Contexts is not null) && !eventHandlers.Any(h => h.Contexts is null)) + { + await _bidi.SessionModule.UnsubscribeAsync([eventHandler.EventName]).ConfigureAwait(false); + } } } } diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs index 09e0a8564ce86..a54b4887a4b06 100644 --- a/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs +++ b/dotnet/src/webdriver/BiDi/Communication/Json/BiDiJsonSerializerContext.cs @@ -79,7 +79,9 @@ namespace OpenQA.Selenium.BiDi.Communication.Json; [JsonSerializable(typeof(Modules.Session.NewResult))] [JsonSerializable(typeof(Modules.Session.EndCommand))] [JsonSerializable(typeof(Modules.Session.SubscribeCommand))] -[JsonSerializable(typeof(Modules.Session.UnsubscribeCommand))] +[JsonSerializable(typeof(Modules.Session.SubscribeResult))] +[JsonSerializable(typeof(Modules.Session.UnsubscribeByIdCommand))] +[JsonSerializable(typeof(Modules.Session.UnsubscribeByAttributesCommand))] [JsonSerializable(typeof(Modules.Browser.CloseCommand), TypeInfoPropertyName = "Browser_CloseCommand")] [JsonSerializable(typeof(Modules.Browser.CreateUserContextCommand))] diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/SubscriptionConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/SubscriptionConverter.cs new file mode 100644 index 0000000000000..606f2a86a85e3 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/SubscriptionConverter.cs @@ -0,0 +1,41 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +#nullable enable + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class SubscriptionConverter : JsonConverter +{ + public override Modules.Session.Subscription? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new Modules.Session.Subscription(id!); + } + + public override void Write(Utf8JsonWriter writer, Modules.Session.Subscription value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/SessionModule.cs b/dotnet/src/webdriver/BiDi/Modules/Session/SessionModule.cs index 4921657ef138a..c26969a2b59a0 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Session/SessionModule.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Session/SessionModule.cs @@ -32,7 +32,7 @@ public async Task StatusAsync(StatusOptions? options = null) return await Broker.ExecuteCommandAsync(new StatusCommand(), options).ConfigureAwait(false); } - public async Task SubscribeAsync(IEnumerable events, SubscribeOptions? options = null) + public async Task SubscribeAsync(IEnumerable events, SubscribeOptions? options = null) { var @params = new SubscribeCommandParameters(events); @@ -41,19 +41,23 @@ public async Task SubscribeAsync(IEnumerable events, SubscribeOptions? o @params.Contexts = options.Contexts; } - await Broker.ExecuteCommandAsync(new SubscribeCommand(@params), options).ConfigureAwait(false); + return await Broker.ExecuteCommandAsync(new(@params), options).ConfigureAwait(false); } - public async Task UnsubscribeAsync(IEnumerable events, UnsubscribeOptions? options = null) + public async Task UnsubscribeAsync(IEnumerable subscriptions, UnsubscribeByIdOptions? options = null) { - var @params = new SubscribeCommandParameters(events); + var @params = new UnsubscribeByIdCommandParameters(subscriptions); - if (options is not null) - { - @params.Contexts = options.Contexts; - } + await Broker.ExecuteCommandAsync(new UnsubscribeByIdCommand(@params), options).ConfigureAwait(false); + } + + public async Task UnsubscribeAsync(IEnumerable eventNames = null, UnsubscribeByAttributesOptions? options = null) + { + var @params = new UnsubscribeByAttributesCommandParameters(eventNames); + + @params.Contexts = options?.Contexts; - await Broker.ExecuteCommandAsync(new UnsubscribeCommand(@params), options).ConfigureAwait(false); + await Broker.ExecuteCommandAsync(new UnsubscribeByAttributesCommand(@params), options).ConfigureAwait(false); } public async Task NewAsync(CapabilitiesRequest capabilitiesRequest, NewOptions? options = null) diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/SubscribeCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Session/SubscribeCommand.cs index fc85306cf269d..44cd432028552 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Session/SubscribeCommand.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Session/SubscribeCommand.cs @@ -36,3 +36,5 @@ public record SubscribeOptions : CommandOptions { public IEnumerable? Contexts { get; set; } } + +internal record SubscribeResult(Subscription Subscription); diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/Subscription.cs b/dotnet/src/webdriver/BiDi/Modules/Session/Subscription.cs new file mode 100644 index 0000000000000..98e45e4119699 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/Subscription.cs @@ -0,0 +1,32 @@ +// +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +#nullable enable + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +public sealed class Subscription +{ + internal Subscription(string id) + { + Id = id; + } + + internal string Id { get; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/UnsubscribeCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Session/UnsubscribeCommand.cs index 970f6c32dc8a1..72f8a28bf8e61 100644 --- a/dotnet/src/webdriver/BiDi/Modules/Session/UnsubscribeCommand.cs +++ b/dotnet/src/webdriver/BiDi/Modules/Session/UnsubscribeCommand.cs @@ -18,12 +18,31 @@ // using OpenQA.Selenium.BiDi.Communication; +using System; +using System.Collections.Generic; #nullable enable namespace OpenQA.Selenium.BiDi.Modules.Session; -internal class UnsubscribeCommand(SubscribeCommandParameters @params) - : Command(@params, "session.unsubscribe"); +internal class UnsubscribeByIdCommand(UnsubscribeByIdCommandParameters @params) + : Command(@params, "session.unsubscribe"); -public record UnsubscribeOptions : SubscribeOptions; +internal class UnsubscribeByAttributesCommand(UnsubscribeByAttributesCommandParameters @params) + : Command(@params, "session.unsubscribe"); + +internal record UnsubscribeByIdCommandParameters(IEnumerable Subscriptions) : CommandParameters; + +public record UnsubscribeByIdOptions : CommandOptions; + +internal record UnsubscribeByAttributesCommandParameters(IEnumerable Events) : CommandParameters +{ + [Obsolete("Contexts param is deprecated and will be removed in the future versions")] + // https://w3c.github.io/webdriver-bidi/#type-session-UnsubscribeByAttributesRequest + public IEnumerable? Contexts { get; set; } +} + +public record UnsubscribeByAttributesOptions : CommandOptions +{ + public IEnumerable? Contexts { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Subscription.cs b/dotnet/src/webdriver/BiDi/Subscription.cs index 2a3b6fc0d8eb2..bf47137f00ccc 100644 --- a/dotnet/src/webdriver/BiDi/Subscription.cs +++ b/dotnet/src/webdriver/BiDi/Subscription.cs @@ -28,18 +28,20 @@ namespace OpenQA.Selenium.BiDi; public class Subscription : IAsyncDisposable { + private readonly Modules.Session.Subscription _subscription; private readonly Broker _broker; private readonly Communication.EventHandler _eventHandler; - internal Subscription(Broker broker, Communication.EventHandler eventHandler) + internal Subscription(Modules.Session.Subscription subscription, Broker broker, Communication.EventHandler eventHandler) { + _subscription = subscription; _broker = broker; _eventHandler = eventHandler; } public async Task UnsubscribeAsync() { - await _broker.UnsubscribeAsync(_eventHandler).ConfigureAwait(false); + await _broker.UnsubscribeAsync(_subscription, _eventHandler).ConfigureAwait(false); } public async ValueTask DisposeAsync()