diff --git a/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsBiDi.cs b/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsBiDi.cs new file mode 100644 index 0000000000000..c70ed1812c91b --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsBiDi.cs @@ -0,0 +1,45 @@ +// +// 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 OpenQA.Selenium.BiDi.Communication; +using System; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Extensions.Permissions; + +public class PermissionsBiDi : BiDi +{ + private readonly Lazy _permissionsModule; + + public PermissionsModule Permissions => _permissionsModule.Value; + + private PermissionsBiDi(string url) + : base(url) + { + Broker.ProvideCustomSerializationContext(PermissionsModule.SerializerContext); + _permissionsModule = new Lazy(() => new PermissionsModule(Broker)); + } + + public static async Task ConnectAsync(string webSocketUrl) + { + var bidi = new PermissionsBiDi(webSocketUrl); + await bidi.Broker.ConnectAsync(); + return bidi; + } +} diff --git a/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsBiDiJsonSerializerContext.cs b/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsBiDiJsonSerializerContext.cs new file mode 100644 index 0000000000000..45aa7fb66d809 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsBiDiJsonSerializerContext.cs @@ -0,0 +1,25 @@ +// +// 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.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Extensions.Permissions; + +[JsonSerializable(typeof(SetPermissionCommand), TypeInfoPropertyName = "Permissions_SetPermissionCommand")] +internal partial class PermissionsBiDiJsonSerializerContext : JsonSerializerContext; diff --git a/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsExtensions.cs b/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsExtensions.cs new file mode 100644 index 0000000000000..ee09601f11af5 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsExtensions.cs @@ -0,0 +1,44 @@ +// +// 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.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Extensions.Permissions; + +public static class PermissionsExtensions +{ + public static async Task AsBiDiPermissionsAsync(this IWebDriver webDriver) + { + if (webDriver is null) throw new ArgumentNullException(nameof(webDriver)); + + string? webSocketUrl = null; + + if (webDriver is IHasCapabilities hasCapabilities) + { + webSocketUrl = hasCapabilities.Capabilities.GetCapability("webSocketUrl")?.ToString(); + } + + if (webSocketUrl is null) throw new BiDiException("The driver is not compatible with bidirectional protocol or \"webSocketUrl\" not enabled in driver options."); + + var bidi = await PermissionsBiDi.ConnectAsync(webSocketUrl).ConfigureAwait(false); + + return bidi; + } +} diff --git a/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsModule.cs b/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsModule.cs new file mode 100644 index 0000000000000..3a7c81021f6fe --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Extensions/Permissions/PermissionsModule.cs @@ -0,0 +1,38 @@ +// +// 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 OpenQA.Selenium.BiDi.Communication; +using OpenQA.Selenium.BiDi.Modules; +using OpenQA.Selenium.BiDi.Modules.Browser; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Extensions.Permissions; + +public class PermissionsModule(Broker broker) : Module(broker) +{ + public static JsonSerializerContext SerializerContext => PermissionsBiDiJsonSerializerContext.Default; + + public async Task SetPermissionAsync(string permissionName, PermissionState state, string origin, UserContext? userContext, SetPermissionOptions? options = null) + { + var @params = new SetPermissionCommandParameters(new PermissionDescriptor(permissionName), state, origin, userContext); + + await Broker.ExecuteCommandAsync(new SetPermissionCommand(@params), options).ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Extensions/Permissions/SetPermissionCommand.cs b/dotnet/src/webdriver/BiDi/Extensions/Permissions/SetPermissionCommand.cs new file mode 100644 index 0000000000000..2bff4df15220b --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Extensions/Permissions/SetPermissionCommand.cs @@ -0,0 +1,39 @@ +// +// 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 OpenQA.Selenium.BiDi.Communication; +using OpenQA.Selenium.BiDi.Modules.Browser; + +namespace OpenQA.Selenium.BiDi.Extensions.Permissions; + +internal class SetPermissionCommand(SetPermissionCommandParameters @params) + : Command(@params, "permissions.setPermission"); + +public record SetPermissionOptions : CommandOptions; + +internal record SetPermissionCommandParameters(PermissionDescriptor Descriptor, PermissionState State, string Origin, UserContext? UserContext) : CommandParameters; + +internal record PermissionDescriptor(string Name); + +public enum PermissionState +{ + Granted, + Denied, + Prompt +} diff --git a/dotnet/test/common/BiDi/BiDiFixture.cs b/dotnet/test/common/BiDi/BiDiFixture.cs index d81501a3df668..68de58ebfb657 100644 --- a/dotnet/test/common/BiDi/BiDiFixture.cs +++ b/dotnet/test/common/BiDi/BiDiFixture.cs @@ -44,11 +44,16 @@ public async Task BiDiSetUp() driver = EnvironmentManager.Instance.CreateDriverInstance(options); - bidi = await driver.AsBiDiAsync(); + bidi = await CreateBiDi(driver); context = (await bidi.BrowsingContext.GetTreeAsync())[0].Context; } + protected virtual async Task CreateBiDi(IWebDriver driver) + { + return await driver.AsBiDiAsync(); + } + [TearDown] public async Task BiDiTearDown() { diff --git a/dotnet/test/common/BiDi/Permissions/PermissionsTests.cs b/dotnet/test/common/BiDi/Permissions/PermissionsTests.cs new file mode 100644 index 0000000000000..cc00f10aa90dc --- /dev/null +++ b/dotnet/test/common/BiDi/Permissions/PermissionsTests.cs @@ -0,0 +1,70 @@ +// +// 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 NUnit.Framework; +using OpenQA.Selenium.BiDi.Extensions.Permissions; +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using OpenQA.Selenium.BiDi.Modules.Script; +using OpenQA.Selenium.Environment; +using System; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Permissions +{ + internal class PermissionsTests : BiDiTestFixture + { + private new PermissionsBiDi bidi => (PermissionsBiDi)base.bidi; + + protected override async Task CreateBiDi(IWebDriver driver) + { + return await driver.AsBiDiPermissionsAsync(); + } + + [Test] + public async Task SettingPermissionsTest() + { + var userContext = await bidi.Browser.CreateUserContextAsync(); + var window = await bidi.BrowsingContext.CreateAsync(ContextType.Window, new() + { + ReferenceContext = context, + UserContext = userContext.UserContext, + Background = true + }); + + var newPage = EnvironmentManager.Instance.UrlBuilder.CreateInlinePage(new InlinePage() + .WithBody("
new page
")); + + await window.NavigateAsync(newPage); + + var before = await window.Script.CallFunctionAsync(""" + async () => (await navigator.permissions.query({ name: "geolocation" })).state + """, awaitPromise: true, new() { UserActivation = true, }); + + Assert.That(before.Result, Is.EqualTo(new RemoteValue.String("prompt"))); + + await bidi.Permissions.SetPermissionAsync("geolocation", PermissionState.Denied, newPage, userContext.UserContext); + + var after = await window.Script.CallFunctionAsync(""" + async () => (await navigator.permissions.query({ name: "geolocation" })).state + """, awaitPromise: true, new() { UserActivation = true }); + + Assert.That(after.Result, Is.EqualTo(new RemoteValue.String("denied"))); + } + } +}