Skip to content

Commit 45a6759

Browse files
committed
Merge remote-tracking branch 'origin/trunk' into dynamic-grid
2 parents e309cde + 4aee006 commit 45a6759

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+423
-455
lines changed

dotnet/src/webdriver/CookieJar.cs

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,20 @@ public ReadOnlyCollection<Cookie> AllCookies
3636
{
3737
Response response = driver.InternalExecute(DriverCommand.GetAllCookies, new Dictionary<string, object>());
3838

39-
try
39+
List<Cookie> toReturn = new List<Cookie>();
40+
if (response.Value is object?[] cookies)
4041
{
41-
List<Cookie> toReturn = new List<Cookie>();
42-
if (response.Value is object?[] cookies)
42+
foreach (object? rawCookie in cookies)
4343
{
44-
foreach (object? rawCookie in cookies)
44+
if (rawCookie != null)
4545
{
46-
if (rawCookie != null)
47-
{
48-
Cookie newCookie = Cookie.FromDictionary((Dictionary<string, object?>)rawCookie);
49-
toReturn.Add(newCookie);
50-
}
46+
Cookie newCookie = Cookie.FromDictionary((Dictionary<string, object?>)rawCookie);
47+
toReturn.Add(newCookie);
5148
}
5249
}
53-
54-
return new ReadOnlyCollection<Cookie>(toReturn);
55-
}
56-
catch (Exception e)
57-
{
58-
throw new WebDriverException("Unexpected problem getting cookies", e);
5950
}
51+
52+
return new ReadOnlyCollection<Cookie>(toReturn);
6053
}
6154
}
6255

@@ -124,22 +117,21 @@ public void DeleteAllCookies()
124117
/// <returns>A Cookie from the name; or <see langword="null"/> if not found.</returns>
125118
public Cookie? GetCookieNamed(string name)
126119
{
127-
if (name is null)
120+
if (string.IsNullOrWhiteSpace(name))
128121
{
129-
throw new ArgumentNullException(nameof(name));
122+
throw new ArgumentException("Cookie name cannot be empty", nameof(name));
130123
}
131124

132-
133-
foreach (Cookie currentCookie in this.AllCookies)
125+
try
134126
{
135-
if (name.Equals(currentCookie.Name))
136-
{
137-
return currentCookie;
138-
}
127+
var rawCookie = driver.InternalExecute(DriverCommand.GetCookie, new() { { "name", name } }).Value;
139128

129+
return Cookie.FromDictionary((Dictionary<string, object>)rawCookie);
130+
}
131+
catch (NoSuchCookieException)
132+
{
133+
return null;
140134
}
141-
142-
return null;
143135
}
144136
}
145137
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// <copyright file="DevToolsJsonOptions.cs" company="Selenium Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
// </copyright>
19+
20+
using System.Text.Json;
21+
22+
#nullable enable
23+
24+
namespace OpenQA.Selenium.DevTools.Json;
25+
26+
internal static class DevToolsJsonOptions
27+
{
28+
public static JsonSerializerOptions Default { get; } = new JsonSerializerOptions()
29+
{
30+
Converters =
31+
{
32+
new StringConverter(),
33+
}
34+
};
35+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// <copyright file="StringConverter.cs" company="Selenium Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
// </copyright>
19+
20+
using System;
21+
using System.Text;
22+
using System.Text.Json;
23+
using System.Text.Json.Serialization;
24+
25+
#nullable enable
26+
27+
namespace OpenQA.Selenium.DevTools.Json;
28+
29+
internal sealed class StringConverter : JsonConverter<string>
30+
{
31+
public override bool HandleNull => true;
32+
33+
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
34+
{
35+
try
36+
{
37+
return reader.GetString();
38+
}
39+
catch (InvalidOperationException)
40+
{
41+
// Fallback to read the value as bytes instead of string.
42+
// System.Text.Json library throws exception when CDP remote end sends non-encoded string as binary data.
43+
// Using JavaScriptEncoder.UnsafeRelaxedJsonEscaping doesn't help because the string actually is byte[].
44+
// https://chromedevtools.github.io/devtools-protocol/tot/Network/#type-Request - here "postData" property
45+
// is a string, which we cannot deserialize properly. This property is marked as deprecated, and new "postDataEntries"
46+
// is suggested for using, where most likely it is base64 encoded.
47+
48+
var bytes = reader.ValueSpan;
49+
var sb = new StringBuilder(bytes.Length);
50+
foreach (byte b in bytes)
51+
{
52+
sb.Append(Convert.ToChar(b));
53+
}
54+
55+
return sb.ToString();
56+
}
57+
}
58+
59+
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
60+
writer.WriteStringValue(value);
61+
}

dotnet/src/webdriver/ICookieJar.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public interface ICookieJar
4747
/// <param name="name">The name of the cookie to retrieve.</param>
4848
/// <returns>The <see cref="Cookie"/> containing the name. Returns <see langword="null"/>
4949
/// if no cookie with the specified name is found.</returns>
50-
/// <exception cref="ArgumentNullException">If <paramref name="name"/> is <see langword="null"/>.</exception>
50+
/// <exception cref="ArgumentException">If <paramref name="name"/> is <see langword="null"/> or <see cref="string.Empty"/>.</exception>
5151
Cookie? GetCookieNamed(string name);
5252

5353
/// <summary>

dotnet/src/webdriver/Internal/Logging/LogContext.cs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System;
2121
using System.Collections.Concurrent;
2222
using System.Collections.Generic;
23+
using System.Diagnostics.CodeAnalysis;
2324
using System.Linq;
2425

2526
#nullable enable
@@ -30,7 +31,7 @@ namespace OpenQA.Selenium.Internal.Logging
3031
/// Represents a logging context that provides methods for creating sub-contexts, retrieving loggers, emitting log messages, and configuring minimum log levels.
3132
/// </summary>
3233
/// <inheritdoc cref="ILogContext"/>
33-
internal class LogContext : ILogContext
34+
internal sealed class LogContext : ILogContext
3435
{
3536
private ConcurrentDictionary<Type, ILogger>? _loggers;
3637

@@ -46,7 +47,7 @@ public LogContext(LogEventLevel level, ILogContext? parentLogContext, Concurrent
4647

4748
_parentLogContext = parentLogContext;
4849

49-
_loggers = loggers;
50+
_loggers = CloneLoggers(loggers, level);
5051

5152
if (handlers is not null)
5253
{
@@ -65,12 +66,7 @@ public ILogContext CreateContext()
6566

6667
public ILogContext CreateContext(LogEventLevel minimumLevel)
6768
{
68-
ConcurrentDictionary<Type, ILogger>? loggers = null;
69-
70-
if (_loggers != null)
71-
{
72-
loggers = new ConcurrentDictionary<Type, ILogger>(_loggers.Select(l => new KeyValuePair<Type, ILogger>(l.Key, new Logger(l.Value.Issuer, minimumLevel))));
73-
}
69+
ConcurrentDictionary<Type, ILogger>? loggers = CloneLoggers(_loggers, minimumLevel);
7470

7571
var context = new LogContext(minimumLevel, this, loggers, Handlers);
7672

@@ -98,7 +94,7 @@ public ILogger GetLogger(Type type)
9894

9995
public bool IsEnabled(ILogger logger, LogEventLevel level)
10096
{
101-
return Handlers != null && level >= _level && level >= logger.Level;
97+
return Handlers != null && level >= _level && (_loggers?.TryGetValue(logger.Issuer, out var loggerEntry) != true || level >= loggerEntry?.Level);
10298
}
10399

104100
public void EmitMessage(ILogger logger, LogEventLevel level, string message)
@@ -155,5 +151,24 @@ public void Dispose()
155151

156152
Log.CurrentContext = _parentLogContext;
157153
}
154+
155+
[return: NotNullIfNotNull(nameof(loggers))]
156+
private static ConcurrentDictionary<Type, ILogger>? CloneLoggers(ConcurrentDictionary<Type, ILogger>? loggers, LogEventLevel minimumLevel)
157+
{
158+
if (loggers is null)
159+
{
160+
return null;
161+
}
162+
163+
var cloned = new Dictionary<Type, ILogger>(loggers.Count);
164+
165+
foreach (KeyValuePair<Type, ILogger> logger in loggers)
166+
{
167+
var clonedLogger = new Logger(logger.Value.Issuer, minimumLevel);
168+
cloned.Add(logger.Key, clonedLogger);
169+
}
170+
171+
return new ConcurrentDictionary<Type, ILogger>(cloned);
172+
}
158173
}
159174
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// <copyright file="NoSuchCookieException.cs" company="Selenium Committers">
2+
// Licensed to the Software Freedom Conservancy (SFC) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The SFC licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
// </copyright>
19+
20+
using System;
21+
22+
#nullable enable
23+
24+
namespace OpenQA.Selenium
25+
{
26+
/// <summary>
27+
/// The exception that is thrown when a cookie is not found.
28+
/// </summary>
29+
[Serializable]
30+
public class NoSuchCookieException : NotFoundException
31+
{
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="NoSuchCookieException"/> class.
34+
/// </summary>
35+
public NoSuchCookieException()
36+
: base()
37+
{
38+
}
39+
40+
/// <summary>
41+
/// Initializes a new instance of the <see cref="NoSuchCookieException"/> class with
42+
/// a specified error message.
43+
/// </summary>
44+
/// <param name="message">The message that describes the error.</param>
45+
public NoSuchCookieException(string? message)
46+
: base(message)
47+
{
48+
}
49+
50+
/// <summary>
51+
/// Initializes a new instance of the <see cref="NoSuchCookieException"/> class with
52+
/// a specified error message and a reference to the inner exception that is the
53+
/// cause of this exception.
54+
/// </summary>
55+
/// <param name="message">The error message that explains the reason for the exception.</param>
56+
/// <param name="innerException">The exception that is the cause of the current exception,
57+
/// or <see langword="null"/> if no inner exception is specified.</param>
58+
public NoSuchCookieException(string? message, Exception? innerException)
59+
: base(message, innerException)
60+
{
61+
}
62+
}
63+
}

dotnet/src/webdriver/Remote/W3CWireProtocolCommandInfoRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ protected override void InitializeCommandDictionary()
104104
this.TryAddCommand(DriverCommand.ExecuteScript, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/execute/sync"));
105105
this.TryAddCommand(DriverCommand.ExecuteAsyncScript, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/execute/async"));
106106
this.TryAddCommand(DriverCommand.GetAllCookies, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/cookie"));
107-
this.TryAddCommand(DriverCommand.GetCookie, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/cookie/{name}"));
107+
this.TryAddCommand(DriverCommand.GetCookie, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/cookie/{name}"));
108108
this.TryAddCommand(DriverCommand.AddCookie, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/cookie"));
109109
this.TryAddCommand(DriverCommand.DeleteCookie, new HttpCommandInfo(HttpCommandInfo.DeleteCommand, "/session/{sessionId}/cookie/{name}"));
110110
this.TryAddCommand(DriverCommand.DeleteAllCookies, new HttpCommandInfo(HttpCommandInfo.DeleteCommand, "/session/{sessionId}/cookie"));

dotnet/src/webdriver/WebDriver.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,9 @@ private static void UnpackAndThrowOnError(Response errorResponse, string command
847847
case WebDriverResult.UnsupportedOperation:
848848
throw new UnsupportedOperationException(errorMessage);
849849

850+
case WebDriverResult.NoSuchCookie:
851+
throw new NoSuchCookieException(errorMessage);
852+
850853
default:
851854
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "{0} ({1})", errorMessage, errorResponse.Status));
852855
}

dotnet/src/webdriver/cdp/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ add an entry for version `<N>` to the `SupportedDevToolsVersions` dictionary ini
1717
6. In [`//dotnet/src/webdriver:WebDriver.csproj.prebuild.cmd`](https://github.com/SeleniumHQ/selenium/blob/trunk/dotnet/src/webdriver/WebDriver.csproj.prebuild.cmd),
1818
add the following block (substituting the proper value for `<N>`):
1919

20-
```
20+
```bash
2121
if not exist "%1..\..\..\bazel-bin\dotnet\src\webdriver\cdp\v<N>\DevToolsSessionDomains.cs" (
2222
echo Generating CDP code for version <N>
2323
pushd "%1..\..\.."
@@ -29,7 +29,7 @@ if not exist "%1..\..\..\bazel-bin\dotnet\src\webdriver\cdp\v<N>\DevToolsSessio
2929
7. In [`//dotnet/src/webdriver:WebDriver.csproj.prebuild.sh`](https://github.com/SeleniumHQ/selenium/blob/trunk/dotnet/src/webdriver/WebDriver.csproj.prebuild.sh),
3030
add the following block (substituting the proper value for `<N>`):
3131
32-
```
32+
```bash
3333
if [[ ! -f "$1../../../bazel-bin/dotnet/src/webdriver/cdp/v<N>/DevToolsSessionDomains.cs" ]]
3434
then
3535
echo "Generating CDP code for version <N>"

0 commit comments

Comments
 (0)