Skip to content

Commit e135af5

Browse files
Merge branch 'trunk' into update-renovate-json
2 parents 0ede3fd + 83d52f8 commit e135af5

File tree

12 files changed

+363
-112
lines changed

12 files changed

+363
-112
lines changed

dotnet/src/webdriver/Firefox/FirefoxProfile.cs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -296,24 +296,14 @@ private void UpdateUserPreferences()
296296

297297
private void ReadDefaultPreferences()
298298
{
299-
var jsonSerializerOptions = new JsonSerializerOptions
300-
{
301-
Converters =
302-
{
303-
new ResponseValueJsonConverter()
304-
}
305-
};
306-
307299
using (Stream defaultPrefsStream = ResourceUtilities.GetResourceStream("webdriver_prefs.json", "webdriver_prefs.json"))
308300
{
309-
using (StreamReader reader = new StreamReader(defaultPrefsStream))
310-
{
311-
string defaultPreferences = reader.ReadToEnd();
312-
Dictionary<string, object> deserializedPreferences = JsonSerializer.Deserialize<Dictionary<string, object>>(defaultPreferences, jsonSerializerOptions);
313-
Dictionary<string, object> immutableDefaultPreferences = deserializedPreferences["frozen"] as Dictionary<string, object>;
314-
Dictionary<string, object> editableDefaultPreferences = deserializedPreferences["mutable"] as Dictionary<string, object>;
315-
this.profilePreferences = new Preferences(immutableDefaultPreferences, editableDefaultPreferences);
316-
}
301+
using JsonDocument defaultPreferences = JsonDocument.Parse(defaultPrefsStream);
302+
303+
JsonElement immutableDefaultPreferences = defaultPreferences.RootElement.GetProperty("frozen");
304+
JsonElement editableDefaultPreferences = defaultPreferences.RootElement.GetProperty("mutable");
305+
306+
this.profilePreferences = new Preferences(immutableDefaultPreferences, editableDefaultPreferences);
317307
}
318308
}
319309

dotnet/src/webdriver/Firefox/Preferences.cs

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
using System.Collections.Generic;
2222
using System.Globalization;
2323
using System.IO;
24+
using System.Text.Json;
25+
26+
#nullable enable
2427

2528
namespace OpenQA.Selenium.Firefox
2629
{
@@ -29,31 +32,27 @@ namespace OpenQA.Selenium.Firefox
2932
/// </summary>
3033
internal class Preferences
3134
{
32-
private Dictionary<string, string> preferences = new Dictionary<string, string>();
33-
private Dictionary<string, string> immutablePreferences = new Dictionary<string, string>();
35+
private readonly Dictionary<string, string> preferences = new Dictionary<string, string>();
36+
private readonly HashSet<string> immutablePreferences = new HashSet<string>();
3437

3538
/// <summary>
3639
/// Initializes a new instance of the <see cref="Preferences"/> class.
3740
/// </summary>
3841
/// <param name="defaultImmutablePreferences">A set of preferences that cannot be modified once set.</param>
3942
/// <param name="defaultPreferences">A set of default preferences.</param>
40-
public Preferences(Dictionary<string, object> defaultImmutablePreferences, Dictionary<string, object> defaultPreferences)
43+
public Preferences(JsonElement defaultImmutablePreferences, JsonElement defaultPreferences)
4144
{
42-
if (defaultImmutablePreferences != null)
45+
foreach (JsonProperty pref in defaultImmutablePreferences.EnumerateObject())
4346
{
44-
foreach (KeyValuePair<string, object> pref in defaultImmutablePreferences)
45-
{
46-
this.SetPreferenceValue(pref.Key, pref.Value);
47-
this.immutablePreferences.Add(pref.Key, pref.Value.ToString());
48-
}
47+
this.ThrowIfPreferenceIsImmutable(pref.Name, pref.Value);
48+
this.preferences[pref.Name] = pref.Value.GetRawText();
49+
this.immutablePreferences.Add(pref.Name);
4950
}
5051

51-
if (defaultPreferences != null)
52+
foreach (JsonProperty pref in defaultPreferences.EnumerateObject())
5253
{
53-
foreach (KeyValuePair<string, object> pref in defaultPreferences)
54-
{
55-
this.SetPreferenceValue(pref.Key, pref.Value);
56-
}
54+
this.ThrowIfPreferenceIsImmutable(pref.Name, pref.Value);
55+
this.preferences[pref.Name] = pref.Value.GetRawText();
5756
}
5857
}
5958

@@ -64,9 +63,31 @@ public Preferences(Dictionary<string, object> defaultImmutablePreferences, Dicti
6463
/// <param name="value">A <see cref="string"/> value give the preference.</param>
6564
/// <remarks>If the preference already exists in the currently-set list of preferences,
6665
/// the value will be updated.</remarks>
66+
/// <exception cref="ArgumentNullException">If <paramref name="key"/> or <paramref name="value"/> are <see langword="null"/>.</exception>
67+
/// <exception cref="ArgumentException">
68+
/// <para>If <paramref name="value"/> is wrapped with double-quotes.</para>
69+
/// <para>-or-</para>
70+
/// <para>If the specified preference is immutable.</para>
71+
/// </exception>
6772
internal void SetPreference(string key, string value)
6873
{
69-
this.SetPreferenceValue(key, value);
74+
if (key is null)
75+
{
76+
throw new ArgumentNullException(nameof(key));
77+
}
78+
79+
if (value is null)
80+
{
81+
throw new ArgumentNullException(nameof(value));
82+
}
83+
84+
if (IsWrappedAsString(value))
85+
{
86+
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Preference values must be plain strings: {0}: {1}", key, value));
87+
}
88+
89+
this.ThrowIfPreferenceIsImmutable(key, value);
90+
this.preferences[key] = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", value);
7091
}
7192

7293
/// <summary>
@@ -76,9 +97,17 @@ internal void SetPreference(string key, string value)
7697
/// <param name="value">A <see cref="int"/> value give the preference.</param>
7798
/// <remarks>If the preference already exists in the currently-set list of preferences,
7899
/// the value will be updated.</remarks>
100+
/// <exception cref="ArgumentNullException">If <paramref name="key"/> is <see langword="null"/>.</exception>
101+
/// <exception cref="ArgumentException">If the specified preference is immutable.</exception>
79102
internal void SetPreference(string key, int value)
80103
{
81-
this.SetPreferenceValue(key, value);
104+
if (key is null)
105+
{
106+
throw new ArgumentNullException(nameof(key));
107+
}
108+
109+
this.ThrowIfPreferenceIsImmutable(key, value);
110+
this.preferences[key] = value.ToString(CultureInfo.InvariantCulture);
82111
}
83112

84113
/// <summary>
@@ -88,16 +117,25 @@ internal void SetPreference(string key, int value)
88117
/// <param name="value">A <see cref="bool"/> value give the preference.</param>
89118
/// <remarks>If the preference already exists in the currently-set list of preferences,
90119
/// the value will be updated.</remarks>
120+
/// <exception cref="ArgumentNullException">If <paramref name="key"/> is <see langword="null"/>.</exception>
121+
/// <exception cref="ArgumentException">If the specified preference is immutable.</exception>
91122
internal void SetPreference(string key, bool value)
92123
{
93-
this.SetPreferenceValue(key, value);
124+
if (key is null)
125+
{
126+
throw new ArgumentNullException(nameof(key));
127+
}
128+
129+
this.ThrowIfPreferenceIsImmutable(key, value);
130+
this.preferences[key] = value ? "true" : "false";
94131
}
95132

96133
/// <summary>
97134
/// Gets a preference from the list of preferences.
98135
/// </summary>
99136
/// <param name="preferenceName">The name of the preference to retrieve.</param>
100137
/// <returns>The value of the preference, or an empty string if the preference is not set.</returns>
138+
/// <exception cref="ArgumentNullException">If <paramref name="preferenceName"/> is <see langword="null"/>.</exception>
101139
internal string GetPreference(string preferenceName)
102140
{
103141
if (this.preferences.ContainsKey(preferenceName))
@@ -151,44 +189,18 @@ private static bool IsWrappedAsString(string value)
151189
return value.StartsWith("\"", StringComparison.OrdinalIgnoreCase) && value.EndsWith("\"", StringComparison.OrdinalIgnoreCase);
152190
}
153191

154-
private bool IsSettablePreference(string preferenceName)
192+
private void ThrowIfPreferenceIsImmutable<TValue>(string preferenceName, TValue value)
155193
{
156-
return !this.immutablePreferences.ContainsKey(preferenceName);
157-
}
158-
159-
private void SetPreferenceValue(string key, object value)
160-
{
161-
if (!this.IsSettablePreference(key))
194+
if (this.immutablePreferences.Contains(preferenceName))
162195
{
163-
string message = string.Format(CultureInfo.InvariantCulture, "Preference {0} may not be overridden: frozen value={1}, requested value={2}", key, this.immutablePreferences[key], value.ToString());
196+
string message = string.Format(CultureInfo.InvariantCulture, "Preference {0} may not be overridden: frozen value={1}, requested value={2}", preferenceName, this.preferences[preferenceName], value?.ToString());
164197
throw new ArgumentException(message);
165198
}
199+
}
166200

167-
string stringValue = value as string;
168-
if (stringValue != null)
169-
{
170-
if (IsWrappedAsString(stringValue))
171-
{
172-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Preference values must be plain strings: {0}: {1}", key, value));
173-
}
174-
175-
this.preferences[key] = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", value);
176-
return;
177-
}
178-
179-
if (value is bool)
180-
{
181-
this.preferences[key] = Convert.ToBoolean(value, CultureInfo.InvariantCulture).ToString().ToLowerInvariant();
182-
return;
183-
}
184-
185-
if (value is int || value is long)
186-
{
187-
this.preferences[key] = Convert.ToInt32(value, CultureInfo.InvariantCulture).ToString(CultureInfo.InvariantCulture);
188-
return;
189-
}
190-
191-
throw new WebDriverException("Value must be string, int or boolean");
201+
private bool IsSettablePreference(string preferenceName)
202+
{
203+
return !this.immutablePreferences.Contains(preferenceName);
192204
}
193205
}
194206
}

java/spotbugs-excludes.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,4 +230,9 @@
230230
<Bug pattern="NM_CLASS_NAMING_CONVENTION"/>
231231
</Match>
232232

233+
<Match>
234+
<Class name="org.openqa.selenium.manager.SeleniumManager"/>
235+
<Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"/>
236+
</Match>
237+
233238
</FindBugsFilter>

java/src/org/openqa/selenium/grid/distributor/local/LocalDistributor.java

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static org.openqa.selenium.remote.RemoteTags.CAPABILITIES_EVENT;
2828
import static org.openqa.selenium.remote.RemoteTags.SESSION_ID;
2929
import static org.openqa.selenium.remote.RemoteTags.SESSION_ID_EVENT;
30+
import static org.openqa.selenium.remote.http.HttpMethod.DELETE;
3031
import static org.openqa.selenium.remote.tracing.AttributeKey.SESSION_URI;
3132
import static org.openqa.selenium.remote.tracing.Tags.EXCEPTION;
3233

@@ -79,6 +80,7 @@
7980
import org.openqa.selenium.grid.data.NodeStatus;
8081
import org.openqa.selenium.grid.data.NodeStatusEvent;
8182
import org.openqa.selenium.grid.data.RequestId;
83+
import org.openqa.selenium.grid.data.Session;
8284
import org.openqa.selenium.grid.data.SessionRequest;
8385
import org.openqa.selenium.grid.data.SessionRequestCapability;
8486
import org.openqa.selenium.grid.data.Slot;
@@ -109,6 +111,7 @@
109111
import org.openqa.selenium.internal.Require;
110112
import org.openqa.selenium.remote.SessionId;
111113
import org.openqa.selenium.remote.http.HttpClient;
114+
import org.openqa.selenium.remote.http.HttpRequest;
112115
import org.openqa.selenium.remote.tracing.AttributeKey;
113116
import org.openqa.selenium.remote.tracing.AttributeMap;
114117
import org.openqa.selenium.remote.tracing.Span;
@@ -853,21 +856,34 @@ private void handleNewSessionRequest(SessionRequest sessionRequest) {
853856
}
854857
}
855858

856-
// 'complete' will return 'true' if the session has not timed out during the creation
857-
// process: it's still a valid session as it can be used by the client
858859
boolean isSessionValid = sessionQueue.complete(reqId, response);
859-
// If the session request has timed out, tell the Node to remove the session, so that does
860-
// not stall
860+
// terminate invalid sessions to avoid stale sessions
861861
if (!isSessionValid && response.isRight()) {
862862
LOG.log(
863863
Level.INFO,
864-
"Session for request {0} has been created but it has timed out, stopping it to avoid"
865-
+ " stalled browser",
864+
"Session for request {0} has been created but it has timed out or the connection"
865+
+ " dropped, stopping it to avoid stalled browser",
866866
reqId.toString());
867-
URI nodeURI = response.right().getSession().getUri();
868-
Node node = getNodeFromURI(nodeURI);
867+
Session session = response.right().getSession();
868+
Node node = getNodeFromURI(session.getUri());
869869
if (node != null) {
870-
node.stop(response.right().getSession().getId());
870+
boolean deleted;
871+
try {
872+
// Attempt to stop the session
873+
deleted =
874+
node.execute(new HttpRequest(DELETE, "/session/" + session.getId())).getStatus()
875+
== 200;
876+
} catch (Exception e) {
877+
LOG.log(
878+
Level.WARNING,
879+
String.format("Exception while trying to delete session %s", session.getId()),
880+
e);
881+
deleted = false;
882+
}
883+
if (!deleted) {
884+
// Kill the session
885+
node.stop(session.getId());
886+
}
871887
}
872888
}
873889
}

java/src/org/openqa/selenium/grid/sessionqueue/local/LocalNewSessionQueue.java

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -220,10 +220,14 @@ public HttpResponse addToQueue(SessionRequest request) {
220220
result = Either.left(new SessionNotCreatedException("New session request timed out"));
221221
}
222222
} catch (InterruptedException e) {
223+
// the client will never see the session, ensure the session is disposed
224+
data.cancel();
223225
Thread.currentThread().interrupt();
224226
result =
225227
Either.left(new SessionNotCreatedException("Interrupted when creating the session", e));
226228
} catch (RuntimeException e) {
229+
// the client will never see the session, ensure the session is disposed
230+
data.cancel();
227231
result =
228232
Either.left(
229233
new SessionNotCreatedException("An error occurred creating the session", e));
@@ -367,43 +371,30 @@ public List<SessionRequest> getNextAvailable(Map<Capabilities, Long> stereotypes
367371
}
368372
}
369373

370-
/** Returns true if the session is still valid (not timed out) */
374+
/** Returns true if the session is still valid (not timed out and not canceled) */
371375
@Override
372376
public boolean complete(
373377
RequestId reqId, Either<SessionNotCreatedException, CreateSessionResponse> result) {
374378
Require.nonNull("New session request", reqId);
375379
Require.nonNull("Result", result);
376380
TraceContext context = contexts.getOrDefault(reqId, tracer.getCurrentContext());
377381
try (Span ignored = context.createSpan("sessionqueue.completed")) {
378-
Lock readLock = lock.readLock();
379-
readLock.lock();
380382
Data data;
381-
boolean isSessionTimedOut = false;
382-
try {
383-
data = requests.get(reqId);
384-
} finally {
385-
readLock.unlock();
386-
}
387-
388-
if (data == null) {
389-
return false;
390-
} else {
391-
isSessionTimedOut = isTimedOut(Instant.now(), data);
392-
}
393-
394383
Lock writeLock = lock.writeLock();
395384
writeLock.lock();
396385
try {
397-
requests.remove(reqId);
386+
data = requests.remove(reqId);
398387
queue.removeIf(req -> reqId.equals(req.getRequestId()));
399388
contexts.remove(reqId);
400389
} finally {
401390
writeLock.unlock();
402391
}
403392

404-
data.setResult(result);
393+
if (data == null) {
394+
return false;
395+
}
405396

406-
return !isSessionTimedOut;
397+
return data.setResult(result);
407398
}
408399
}
409400

@@ -466,6 +457,7 @@ private class Data {
466457
private final CountDownLatch latch = new CountDownLatch(1);
467458
private Either<SessionNotCreatedException, CreateSessionResponse> result;
468459
private boolean complete;
460+
private boolean canceled;
469461

470462
public Data(Instant enqueued) {
471463
this.endTime = enqueued.plus(requestTimeout);
@@ -476,14 +468,19 @@ public synchronized Either<SessionNotCreatedException, CreateSessionResponse> ge
476468
return result;
477469
}
478470

479-
public synchronized void setResult(
471+
public synchronized void cancel() {
472+
canceled = true;
473+
}
474+
475+
public synchronized boolean setResult(
480476
Either<SessionNotCreatedException, CreateSessionResponse> result) {
481-
if (complete) {
482-
return;
477+
if (complete || canceled) {
478+
return false;
483479
}
484480
this.result = result;
485481
complete = true;
486482
latch.countDown();
483+
return true;
487484
}
488485
}
489486
}

java/src/org/openqa/selenium/manager/SeleniumManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ private synchronized Path getBinary() {
213213
if (!binary.toFile().exists()) {
214214
String binaryPathInJar = String.format("%s/%s%s", folder, SELENIUM_MANAGER, extension);
215215
try (InputStream inputStream = this.getClass().getResourceAsStream(binaryPathInJar)) {
216-
binary.getParent().toFile().mkdirs();
216+
Files.createDirectories(binary.getParent());
217217
Files.copy(inputStream, binary);
218218
}
219219
}

0 commit comments

Comments
 (0)