Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@
// under the License.
// </copyright>

using OpenQA.Selenium.BiDi.Json.Converters;
using System.Text.Json.Serialization;

namespace OpenQA.Selenium.BiDi.BrowsingContext;

internal sealed class SetViewportCommand(SetViewportParameters @params)
: Command<SetViewportParameters, SetViewportResult>(@params, "browsingContext.setViewport");

internal sealed record SetViewportParameters(BrowsingContext Context, Viewport? Viewport, double? DevicePixelRatio) : Parameters;
internal sealed record SetViewportParameters(BrowsingContext Context, [property: JsonConverter(typeof(OptionalConverter<Viewport?>))] Optional<Viewport?>? Viewport, [property: JsonConverter(typeof(OptionalConverter<double?>))] Optional<double?>? DevicePixelRatio) : Parameters;

public sealed class SetViewportOptions : CommandOptions
{
public Viewport? Viewport { get; set; }
public Optional<Viewport?>? Viewport { get; set; }

public double? DevicePixelRatio { get; set; }
public Optional<double?>? DevicePixelRatio { get; set; }
}

public readonly record struct Viewport(long Width, long Height);
Expand Down
51 changes: 51 additions & 0 deletions dotnet/src/webdriver/BiDi/Json/Converters/OptionalConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// <copyright file="OptionalConverter.cs" company="Selenium Committers">
// 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.
// </copyright>

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace OpenQA.Selenium.BiDi.Json.Converters;

public sealed class OptionalConverter<T> : JsonConverter<Optional<T>>
{
public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
reader.Read(); // consume null
return new Optional<T>(default!);
}

T value = JsonSerializer.Deserialize<T>(ref reader, options)!;
return new Optional<T>(value);
}

public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options)
{
if (value.TryGetValue(out var optionalValue))
{
JsonSerializer.Serialize(writer, optionalValue, options);
}
else
{
writer.WriteNullValue();
}
}
}
47 changes: 47 additions & 0 deletions dotnet/src/webdriver/BiDi/Optional.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// <copyright file="Optional.cs" company="Selenium Committers">
// 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.
// </copyright>

using System;

namespace OpenQA.Selenium.BiDi;

public readonly record struct Optional<T>
{
private readonly T _value;
public bool HasValue { get; }

public T Value => HasValue
? _value
: throw new InvalidOperationException("Optional has no value. Check IsSet first.");

public Optional(T value)
{
_value = value;
HasValue = true;
}

public bool TryGetValue(out T value)
{
value = _value;
return HasValue;
}

// implicit conversion from T -> Optional<T>
public static implicit operator Optional<T>(T value) => new(value);
}
31 changes: 29 additions & 2 deletions dotnet/test/common/BiDi/BrowsingContext/BrowsingContextTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,40 @@ public async Task CanCaptureScreenshotOfElement()
[Test]
public async Task CanSetViewport()
{
await context.SetViewportAsync(new() { Viewport = new(250, 300) });
Task<int> GetWidthAsync() => context.Script.EvaluateAsync<int>("window.innerWidth", false);
Task<int> GetHeightAsync() => context.Script.EvaluateAsync<int>("window.innerHeight", false);

var defaultWidth = await GetWidthAsync();
var defaultHeight = await GetHeightAsync();

await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) });

Assert.That(await GetWidthAsync(), Is.EqualTo(250));
Assert.That(await GetHeightAsync(), Is.EqualTo(300));

await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) });
await context.SetViewportAsync(); // Sends nothing

Assert.That(await GetWidthAsync(), Is.EqualTo(250));
Assert.That(await GetHeightAsync(), Is.EqualTo(300));

await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) });
await context.SetViewportAsync(new() { Viewport = default }); // Sends nothing

Assert.That(await GetWidthAsync(), Is.EqualTo(250));
Assert.That(await GetHeightAsync(), Is.EqualTo(300));

await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300) });
await context.SetViewportAsync(new() { Viewport = default(Viewport?) }); // Explicitly sends "null", resetting to default

Assert.That(await GetWidthAsync(), Is.EqualTo(defaultWidth));
Assert.That(await GetHeightAsync(), Is.EqualTo(defaultHeight));
}

[Test]
public async Task CanSetViewportWithDevicePixelRatio()
{
await context.SetViewportAsync(new() { Viewport = new(250, 300), DevicePixelRatio = 5 });
await context.SetViewportAsync(new() { Viewport = new Viewport(250, 300), DevicePixelRatio = 5 });
}

[Test]
Expand Down