Skip to content

.pr_agent_auto_best_practices

qodo-merge-bot edited this page Dec 28, 2025 · 14 revisions

Pattern 1: When returning or storing collection-like state (maps/sets), return an immutable/defensive copy and avoid mutating caller-provided collections (especially in constructors) to prevent side effects and accidental external modification.

Example code before:

class Foo {
  private final Map<String, String> labels;
  Foo(Map<String, String> labels) {
    this.labels = labels;              // stores caller map
    this.labels.put("x", "1");         // mutates caller map
  }
  Map<String, String> toMap() { return labels; }  // leaks internals
}

Example code after:

class Foo {
  private final Map<String, String> labels;
  Foo(Map<String, String> labels) {
    Map<String, String> copy = new HashMap<>(labels);
    copy.put("x", "1");
    this.labels = Collections.unmodifiableMap(copy);
  }
  Map<String, String> toMap() { return new HashMap<>(labels); }
}
Relevant past accepted suggestions:
Suggestion 1:

Return unmodifiable serialized map

Return an unmodifiable copy to prevent callers from mutating the returned map; this preserves immutability of serialized state.

java/src/org/openqa/selenium/bidi/emulation/ScreenOrientation.java [46-51]

 public Map<String, Object> toMap() {
   Map<String, Object> map = new HashMap<>();
   map.put("natural", natural.toString());
   map.put("type", type.toString());
-  return map;
+  return java.util.Collections.unmodifiableMap(map);
 }

Suggestion 2:

Return an immutable set copy

In getSessionsByUri, replace new HashSet<>(result) with Set.copyOf(result) to return an immutable copy of the set, which is a safer programming practice.

java/src/org/openqa/selenium/grid/sessionmap/local/LocalSessionMap.java [293-297]

 public Set<SessionId> getSessionsByUri(URI uri) {
   Set<SessionId> result = sessionsByUri.get(uri);
   // Return a copy to prevent concurrent modification issues
-  return (result != null && !result.isEmpty()) ? new HashSet<>(result) : Set.of();
+  return (result != null && !result.isEmpty()) ? Set.copyOf(result) : Set.of();
 }

Suggestion 3:

Avoid mutating a constructor parameter

To avoid mutating a constructor parameter and prevent potential runtime exceptions, create a new mutable copy of the composeLabels map before adding the new label.

java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java [144-146]

-this.composeLabels = Require.nonNull("Docker Compose labels", composeLabels);
 // Merge compose labels with oneoff=False to prevent triggering --exit-code-from dynamic grid
-this.composeLabels.put("com.docker.compose.oneoff", "False");
+Map<String, String> allLabels = new HashMap<>(Require.nonNull("Docker Compose labels", composeLabels));
+allLabels.put("com.docker.compose.oneoff", "False");
+this.composeLabels = Collections.unmodifiableMap(allLabels);

Suggestion 4:

Avoid modifying nested map directly

To avoid side effects, create a defensive copy of the networkSettings map in adaptContainerInspectResponse before modifying it, ensuring the original response data structure remains unchanged.

java/src/org/openqa/selenium/docker/client/V148Adapter.java [165-205]

 @SuppressWarnings("unchecked")
-Map<String, Object> networkSettings = (Map<String, Object>) adapted.get("NetworkSettings");
+Map<String, Object> originalNetworkSettings =
+    (Map<String, Object>) adapted.get("NetworkSettings");
 
-if (networkSettings != null) {
+if (originalNetworkSettings != null) {
+  // Create a mutable copy to avoid modifying the original map
+  Map<String, Object> networkSettings = new HashMap<>(originalNetworkSettings);
+
   @SuppressWarnings("unchecked")
   Map<String, Object> networks = (Map<String, Object>) networkSettings.get("Networks");
 
   if (networks != null) {
     for (Map.Entry<String, Object> entry : networks.entrySet()) {
       if (entry.getValue() instanceof Map) {
         @SuppressWarnings("unchecked")
         Map<String, Object> network = (Map<String, Object>) entry.getValue();
 
         if (network.containsKey("GwPriority")) {
           LOG.fine(
               "Network '" + entry.getKey() + "' includes GwPriority (API v" + apiVersion + ")");
         }
       }
     }
   }
 
   // Remove deprecated fields (should not be present in v1.48+)
   String[] deprecatedFields = {
     "HairpinMode",
     "LinkLocalIPv6Address",
     "LinkLocalIPv6PrefixLen",
     "SecondaryIPAddresses",
     "SecondaryIPv6Addresses",
     "Bridge" // Deprecated in v1.51, removed in v1.52
   };
 
   for (String field : deprecatedFields) {
     if (networkSettings.containsKey(field)) {
       LOG.fine(
           "Removing deprecated field '"
               + field
               + "' from NetworkSettings (deprecated in earlier API versions)");
       networkSettings.remove(field);
     }
   }
+  adapted.put("NetworkSettings", networkSettings);
 }

Suggestion 5:

Return a defensive copy of map

Return a defensive copy of the internal map in the toMap() method to prevent external modification and maintain encapsulation.

java/src/org/openqa/selenium/bidi/emulation/AbstractOverrideParameters.java [57]

-return map;
+return new HashMap<>(map);

Suggestion 6:

Create a defensive copy of parameters

To ensure the Command object is immutable, create a defensive, unmodifiable copy of the params map that allows null values.

java/src/org/openqa/selenium/bidi/Command.java [51]

-this.params = Require.nonNull("Command parameters", params);
+this.params = java.util.Collections.unmodifiableMap(new java.util.HashMap<>(Require.nonNull("Command parameters", params)));

Pattern 2: In tests or code that creates external resources (temporary directories, browsing contexts, user contexts, sessions), wrap usage in try/finally and always perform cleanup/close/delete in the finally block so failures and assertions cannot leak resources.

Example code before:

Path dir = Files.createTempDirectory("test");
Context ctx = api.createContext();
api.doWork(ctx, dir);
api.closeContext(ctx);
Files.deleteIfExists(dir);

Example code after:

Path dir = Files.createTempDirectory("test");
Context ctx = api.createContext();
try {
  api.doWork(ctx, dir);
} finally {
  try { api.closeContext(ctx); } finally { Files.deleteIfExists(dir); }
}
Relevant past accepted suggestions:
Suggestion 1:

Clean up temporary test directories

Add cleanup logic to the finally block to delete the temporary directory created during the test. This prevents resource leaks on the file system.

java/test/org/openqa/selenium/bidi/browser/BrowserCommandsTest.java [113-142]

 Path tmpDir = TemporaryFilesystem.getDefaultTmpFS().createTempDir("downloads", "test").toPath();
 
 try {
   browser.setDownloadBehavior(new SetDownloadBehaviorParameters(true, tmpDir));
 
   BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle());
   String url = appServer.whereIs("downloads/download.html");
   context.navigate(url, ReadinessState.COMPLETE);
 
   driver.findElement(By.id("file-1")).click();
 
   new WebDriverWait(driver, Duration.ofSeconds(5))
       .until(
           d -> {
             try {
               return Files.list(tmpDir)
                   .anyMatch(path -> path.getFileName().toString().equals("file_1.txt"));
             } catch (Exception e) {
               return false;
             }
           });
 
   List<String> fileNames =
       Files.list(tmpDir)
           .map(path -> path.getFileName().toString())
           .collect(Collectors.toList());
   assertThat(fileNames).contains("file_1.txt");
 } finally {
   browser.setDownloadBehavior(new SetDownloadBehaviorParameters(null, (Path) null));
+  TemporaryFilesystem.getDefaultTmpFS().deleteTempDir(tmpDir.toFile());
 }

Suggestion 2:

Cleanup created user contexts

Ensure created user contexts are cleaned up after the test by deleting them in a finally block to prevent resource leaks.

java/test/org/openqa/selenium/bidi/emulation/SetScriptingEnabledTest.java [82-113]

 String userContext = browser.createUserContext();
-BrowsingContext context =
-    new BrowsingContext(
-        driver, new CreateContextParameters(WindowType.TAB).userContext(userContext));
-String contextId = context.getId();
-...
-// Clear the scripting override
-emulation.setScriptingEnabled(
-    new SetScriptingEnabledParameters(null).userContexts(List.of(userContext)));
+try {
+  BrowsingContext context =
+      new BrowsingContext(
+          driver, new CreateContextParameters(WindowType.TAB).userContext(userContext));
+  String contextId = context.getId();
+  ...
+  emulation.setScriptingEnabled(
+      new SetScriptingEnabledParameters(null).userContexts(List.of(userContext)));
+} finally {
+  browser.deleteUserContext(userContext);
+}

Suggestion 3:

Close context in finally

Wrap context creation/usage in try-finally and close the BrowsingContext in finally to avoid leaks if assertions throw.

java/test/org/openqa/selenium/bidi/emulation/SetGeolocationOverrideTest.java [201-235]

 BrowsingContext context = new BrowsingContext(driver, driver.getWindowHandle());
-String contextId = context.getId();
-...
-// Error because there's no real geolocation available
-assertThat(r2.containsKey("error")).isTrue();
+try {
+  String contextId = context.getId();
+  String url = appServer.whereIsSecure("blank.html");
+  context.navigate(url, ReadinessState.COMPLETE);
+  driver.switchTo().window(context.getId());
+  String origin =
+      (String) ((JavascriptExecutor) driver).executeScript("return window.location.origin;");
+  Emulation emul = new Emulation(driver);
+  GeolocationCoordinates coords = new GeolocationCoordinates(37.7749, -122.4194);
+  emul.setGeolocationOverride(
+      new SetGeolocationOverrideParameters(coords).contexts(List.of(contextId)));
+  Object firstResult = getBrowserGeolocation(driver, null, origin);
+  Map<String, Object> r1 = ((Map<String, Object>) firstResult);
+  assertThat(r1.containsKey("error")).isFalse();
+  double latitude1 = ((Number) r1.get("latitude")).doubleValue();
+  double longitude1 = ((Number) r1.get("longitude")).doubleValue();
+  assertThat(abs(latitude1 - coords.getLatitude())).isLessThan(0.0001);
+  assertThat(abs(longitude1 - coords.getLongitude())).isLessThan(0.0001);
+  emul.setGeolocationOverride(
+      new SetGeolocationOverrideParameters((GeolocationCoordinates) null)
+          .contexts(List.of(contextId)));
+  Object secondResult = getBrowserGeolocation(driver, null, origin);
+  Map<String, Object> r2 = ((Map<String, Object>) secondResult);
+  assertThat(r2.containsKey("error")).isTrue();
+} finally {
+  context.close();
+}

Suggestion 4:

Add robust test cleanup

Ensure the tab and user context are cleaned up even if assertions fail by wrapping them in try/finally blocks.

py/test/selenium/webdriver/common/bidi_emulation_tests.py [347-363]

 def test_set_locale_override_with_user_contexts(driver, pages, value):
     """Test setting locale override with user contexts."""
     user_context = driver.browser.create_user_context()
+    try:
+        context_id = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context)
+        try:
+            driver.switch_to.window(context_id)
+            driver.emulation.set_locale_override(locale=value, user_contexts=[user_context])
+            driver.browsing_context.navigate(context_id, pages.url("formPage.html"), wait="complete")
+            current_locale = get_browser_locale(driver)
+            assert current_locale == value, f"Expected locale {value}, got {current_locale}"
+        finally:
+            driver.browsing_context.close(context_id)
+    finally:
+        driver.browser.remove_user_context(user_context)
 
-    context_id = driver.browsing_context.create(type=WindowTypes.TAB, user_context=user_context)
-
-    driver.switch_to.window(context_id)
-
-    driver.emulation.set_locale_override(locale=value, user_contexts=[user_context])
-
-    driver.browsing_context.navigate(context_id, pages.url("formPage.html"), wait="complete")
-
-    current_locale = get_browser_locale(driver)
-    assert current_locale == value, f"Expected locale {value}, got {current_locale}"
-
-    driver.browsing_context.close(context_id)
-    driver.browser.remove_user_context(user_context)
-

Pattern 3: Add explicit validation and null/availability guards at integration boundaries (GitHub Actions contexts, environment variables, URL parameters, JSON/message fields, browser APIs) by trimming inputs, checking presence/type, and encoding/sanitizing before use.

Example code before:

name = params["name"]          # may be nil/blank
url = "/api/create?name=" + name
mode = window.localStorage.getItem("mode")
path = Path(os.getenv("TOOL_PATH"))

Example code after:

name = params["name"]
if (name != null) {
  name = name.trim();
  if (!name.isEmpty()) {
    url = "/api/create?name=" + URLEncoder.encode(name, UTF_8);
  }
}
if (typeof window !== "undefined") {
  try { mode = window.localStorage.getItem("mode"); } catch {}
}
env = os.getenv("TOOL_PATH")
if (env != null && Path(env.strip()).is_file()) { path = Path(env.strip()) }
Relevant past accepted suggestions:
Suggestion 1:

Make fork guard null-safe

For schedule/workflow_dispatch, github.event.repository may be absent, so comparing it to false can incorrectly skip jobs; use != true and keep the owner check separate.

.github/workflows/nightly.yml [37]

-if: (github.event.repository.fork == false == 'seleniumhq') && (inputs.language == 'ruby' || inputs.language == 'all' || github.event_name == 'schedule')
+if: (github.repository_owner == 'seleniumhq' && github.event.repository.fork != true) && (inputs.language == 'ruby' || inputs.language == 'all' || github.event_name == 'schedule')

Suggestion 2:

Validate environment variable path

Validate that SE_MANAGER_PATH points to an existing executable file and ignore or error out if it does not.

py/selenium/webdriver/common/selenium_manager.py [81-83]

 if (env_path := os.getenv("SE_MANAGER_PATH")) is not None:
     logger.debug(f"Selenium Manager set by env SE_MANAGER_PATH to: {env_path}")
-    path = Path(env_path)
+    env_path = env_path.strip()
+    candidate = Path(env_path)
+    if candidate.is_file():
+        path = candidate
+    else:
+        logger.warning(f"SE_MANAGER_PATH does not point to a file: {env_path}")

Suggestion 3:

Validate and encode URL parameter

Validate and URL-encode the optional container name to avoid invalid requests and injection issues. Reject empty/whitespace-only names and percent-encode the value.

java/src/org/openqa/selenium/docker/client/CreateContainer.java [66-69]

 String url = String.format("/v%s/containers/create", apiVersion);
-if (info.getName() != null && !info.getName().isEmpty()) {
-  url += "?name=" + info.getName();
+String name = info.getName();
+if (name != null) {
+  name = name.trim();
+  if (!name.isEmpty()) {
+    String encoded = java.net.URLEncoder.encode(name, java.nio.charset.StandardCharsets.UTF_8);
+    url += "?name=" + encoded;
+  }
 }

Suggestion 4:

Guard message fields before use

Add defensive checks to ensure message['params'] is a Hash before use and safely duplicate callbacks to prevent nil errors and races when handlers are removed concurrently.

rb/lib/selenium/webdriver/common/websocket_connection.rb [137-147]

 while (frame = incoming_frame.next)
   break if @closing
 
   message = process_frame(frame)
-  next unless message['method']
+  method = message['method']
+  next unless method.is_a?(String)
 
   params = message['params']
-  @messages_mtx.synchronize { callbacks[message['method']].dup }.each do |callback|
+  next unless params.is_a?(Hash)
+
+  handlers = @callbacks_mtx.synchronize { callbacks[method].dup }
+  handlers.each do |callback|
     @callback_threads.add(callback_thread(params, &callback))
   end
 end

Suggestion 5:

Default Content-Type when unknown

Ensure a valid Content-Type is always sent by defaulting to a sensible MIME type when guessing fails. This avoids sending a None header value.

py/test/selenium/webdriver/common/webserver.py [104-108]

 elif os.path.isfile(file_path):
     content_type, _ = mimetypes.guess_type(file_path)
+    if not content_type:
+        content_type = "application/octet-stream"
     content = self._serve_file(file_path)
     self._send_response(content_type)
     self.wfile.write(content)

Suggestion 6:

Guard browser API accesses

Add environment checks before using window, localStorage, and matchMedia to avoid runtime errors in SSR/tests and ensure safe access. Fallback gracefully when unavailable.

javascript/grid-ui/src/contexts/ThemeContext.tsx [40-55]

 useEffect(() => {
-  const saved = localStorage.getItem('theme-mode') as ThemeMode
-  if (saved) setThemeMode(saved)
-  setSystemPrefersDark(window.matchMedia('(prefers-color-scheme: dark)').matches)
+  if (typeof window !== 'undefined') {
+    try {
+      const saved = window.localStorage.getItem('theme-mode') as ThemeMode
+      if (saved) setThemeMode(saved)
+      const mq = window.matchMedia?.('(prefers-color-scheme: dark)')
+      setSystemPrefersDark(!!mq && mq.matches)
+    } catch {
+      // ignore storage access errors
+    }
+  }
 }, [])
 
 useEffect(() => {
-  localStorage.setItem('theme-mode', themeMode)
+  if (typeof window !== 'undefined') {
+    try {
+      window.localStorage.setItem('theme-mode', themeMode)
+    } catch {
+      // ignore storage access errors
+    }
+  }
 }, [themeMode])
 
 useEffect(() => {
-  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
-  const handler = (e: MediaQueryListEvent) => setSystemPrefersDark(e.matches)
-  mediaQuery.addEventListener('change', handler)
-  return () => mediaQuery.removeEventListener('change', handler)
+  if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
+    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
+    const handler = (e: MediaQueryListEvent) => setSystemPrefersDark(e.matches)
+    mediaQuery.addEventListener?.('change', handler)
+    return () => mediaQuery.removeEventListener?.('change', handler)
+  }
+  return
 }, [])

Pattern 4: Make lifecycle/concurrency-sensitive code robust by removing hidden side effects, ensuring idempotent disposal, and using thread-safe synchronization/initialization patterns (granular locks, atomic removal, Interlocked/CompareExchange, etc.).

Example code before:

Resource _r;
public Resource R => _r ??= new Resource(); // not thread-safe
public void Dispose() { pool.Return(buf); } // can run twice
callbacks.reject! { |cb| cb.id == id }      // non-atomic with other reads

Example code after:

private volatile Resource? _r;
public Resource R =>
  _r ?? Interlocked.CompareExchange(ref _r, new Resource(), null) ?? _r!;
private bool _disposed;
public void Dispose() { if (_disposed) return; pool.Return(buf); _disposed = true; }
mutex.synchronize { callbacks.reject! { |cb| cb.id == id } or raise "missing" }
Relevant past accepted suggestions:
Suggestion 1:

Remove side-effectful mutation

Avoid mutating SessionId in a helper; instead create a new value or event payload carrying the reason to prevent hidden side effects.

java/src/org/openqa/selenium/grid/data/SessionClosedEvent.java [36-46]

 public SessionClosedEvent(SessionId id, SessionClosedReason reason) {
-  super(SESSION_CLOSED, markSessionId(id, reason));
   Require.nonNull("Session ID", id);
   Require.nonNull("Reason", reason);
+  super(SESSION_CLOSED, id.withCloseReason(reason.getReasonText()));
 }
 
-// Helper method to mark the SessionId before passing to Event constructor
-private static SessionId markSessionId(SessionId id, SessionClosedReason reason) {
-  id.setCloseReason(reason.getReasonText());
-  return id;
-}
-

Suggestion 2:

Fix race condition with thread-safe initialization

The lazy initialization using ??= is not thread-safe and can cause a race condition. To fix this, use Interlocked.CompareExchange for a lock-free, thread-safe initialization and mark the backing fields as volatile.

dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContext.cs [34-58]

-private BrowsingContextLogModule? _logModule;
-private BrowsingContextNetworkModule? _networkModule;
-private BrowsingContextScriptModule? _scriptModule;
-private BrowsingContextStorageModule? _storageModule;
-private BrowsingContextInputModule? _inputModule;
+private volatile BrowsingContextLogModule? _logModule;
+private volatile BrowsingContextNetworkModule? _networkModule;
+private volatile BrowsingContextScriptModule? _scriptModule;
+private volatile BrowsingContextStorageModule? _storageModule;
+private volatile BrowsingContextInputModule? _inputModule;
 
 internal string Id { get; }
 
 [JsonIgnore]
 public BiDi BiDi { get; }
 
 [JsonIgnore]
-public BrowsingContextLogModule Log => _logModule ??= new BrowsingContextLogModule(this, BiDi.Log);
+public BrowsingContextLogModule Log => _logModule ?? Interlocked.CompareExchange(ref _logModule, new BrowsingContextLogModule(this, BiDi.Log), null) ?? _logModule!;
 
 [JsonIgnore]
-public BrowsingContextNetworkModule Network => _networkModule ??= new BrowsingContextNetworkModule(this, BiDi.Network);
+public BrowsingContextNetworkModule Network => _networkModule ?? Interlocked.CompareExchange(ref _networkModule, new BrowsingContextNetworkModule(this, BiDi.Network), null) ?? _networkModule!;
 
 [JsonIgnore]
-public BrowsingContextScriptModule Script => _scriptModule ??= new BrowsingContextScriptModule(this, BiDi.Script);
+public BrowsingContextScriptModule Script => _scriptModule ?? Interlocked.CompareExchange(ref _scriptModule, new BrowsingContextScriptModule(this, BiDi.Script), null) ?? _scriptModule!;
 
 [JsonIgnore]
-public BrowsingContextStorageModule Storage => _storageModule ??= new BrowsingContextStorageModule(this, BiDi.Storage);
+public BrowsingContextStorageModule Storage => _storageModule ?? Interlocked.CompareExchange(ref _storageModule, new BrowsingContextStorageModule(this, BiDi.Storage), null) ?? _storageModule!;
 
 [JsonIgnore]
-public BrowsingContextInputModule Input => _inputModule ??= new BrowsingContextInputModule(this, BiDi.InputModule);
+public BrowsingContextInputModule Input => _inputModule ?? Interlocked.CompareExchange(ref _inputModule, new BrowsingContextInputModule(this, BiDi.InputModule), null) ?? _inputModule!;

Suggestion 3:

Make the Dispose method idempotent

Make the Dispose method idempotent by using a private flag. This prevents returning the same buffer to the ArrayPool multiple times, which would otherwise cause a critical error.

dotnet/src/webdriver/BiDi/WebSocketTransport.cs [91-97]

+private bool _disposed;
+
 public void Dispose()
 {
+    if (_disposed)
+    {
+        return;
+    }
+
     _webSocket.Dispose();
     _sharedMemoryStream.Dispose();
     _socketSendSemaphoreSlim.Dispose();
     ArrayPool<byte>.Shared.Return(_receiveBuffer);
+    _disposed = true;
 }

Suggestion 4:

Refactor to use multiple specialized mutexes

**payload) @@ -98,12 +106,10 @@ begin socket.write(out_frame.to_s) rescue *CONNECTION_ERRORS => e

  •      raise Error::WebDriverError, "WebSocket is closed (#{e.class}: #{e.message})"
    
  •    end
    
  •    wait.until do
    
  •      @mtx.synchronize { messages.delete(id) }
    
  •    end
    
  •      raise e, "WebSocket is closed (#{e.class}: #{e.message})"
    
  •    end
    
  •    wait.until { @messages_mtx.synchronize { messages.delete(id) } }
     end
    
     private
    

@@ -132,9 +138,8 @@ message = process_frame(frame) next unless message['method']

  •          params = message['params']
    
  •          @mtx.synchronize { callbacks[message['method']].dup }.each do |callback|
    
  •            @callback_threads.add(callback_thread(params, &callback))
    
  •          @messages_mtx.synchronize { callbacks[message['method']].dup }.each do |callback|
    
  •            @callback_threads.add(callback_thread(message['params'], &callback))
             end
           end
         end
    

@@ -154,7 +159,7 @@ return {} if message.empty?

     msg = JSON.parse(message)
  •    @mtx.synchronize { messages[msg['id']] = msg if msg.key?('id') }
    
  •    @messages_mtx.synchronize { messages[msg['id']] = msg if msg.key?('id') }
    
       WebDriver.logger.debug "WebSocket <- #{msg}"[...MAX_LOG_MESSAGE_SIZE], id: :ws
       msg
    

@@ -170,7 +175,8 @@ rescue Error::WebDriverError, *CONNECTION_ERRORS => e WebDriver.logger.debug "Callback aborted: #{e.class}: #{e.message}", id: :ws rescue StandardError => e

  •      # Unexpected handler failure; log with a short backtrace.
    
  •      return if @closing
    
  •      bt = Array(e.backtrace).first(5).join("\n")
         WebDriver.logger.error "Callback error: #{e.class}: #{e.message}\n#{bt}", id: :ws
       end
    






**Replace the single, coarse-grained mutex with multiple, more granular mutexes, one for each shared resource (@callbacks, @messages, @closing). This will reduce lock contention and improve concurrency.**


### Examples:





rb/lib/selenium/webdriver/common/websocket_connection.rb [40]




```ruby
        @mtx = Mutex.new

rb/lib/selenium/webdriver/common/websocket_connection.rb [77-80]

        @mtx.synchronize do
          callbacks[event] << block
          block.object_id
        end

Solution Walkthrough:

Before:

class WebSocketConnection
  def initialize(url:)
    @mtx = Mutex.new
    @closing = false
    @callbacks = {}
    @messages = {}
    # ...
  end

  def add_callback(event, )
    @mtx.synchronize do
      # ... access @callbacks
    end
  end

  def send_cmd(**payload)
    # ...
    wait.until do
      @mtx.synchronize { messages.delete(id) }
    end
  end

  def process_frame(frame)
    # ...
    @mtx.synchronize { messages[msg['id']] = msg }
    # ...
  end
end

After:

class WebSocketConnection
  def initialize(url:)
    @callbacks_mtx = Mutex.new
    @messages_mtx = Mutex.new
    @closing_mtx = Mutex.new
    @closing = false
    @callbacks = {}
    @messages = {}
    # ...
  end

  def add_callback(event, )
    @callbacks_mtx.synchronize do
      # ... access @callbacks
    end
  end

  def send_cmd(**payload)
    # ...
    wait.until do
      @messages_mtx.synchronize { messages.delete(id) }
    end
  end

  def process_frame(frame)
    # ...
    @messages_mtx.synchronize { messages[msg['id']] = msg }
    # ...
  end
end

Suggestion 5:

Fix race condition in callback removal

Refactor the remove_callback method to use a single synchronize block. This will prevent a race condition by ensuring the callback removal and error message data collection are performed atomically.

rb/lib/selenium/webdriver/common/websocket_connection.rb [84-88]

-removed = @mtx.synchronize { callbacks[event].reject! { |cb| cb.object_id == id } }
-return if removed || @closing
+@mtx.synchronize do
+  return if @closing
 
-ids = @mtx.synchronize { callbacks[event]&.map(&:object_id) }
-raise Error::WebDriverError, "Callback with ID #{id} does not exist for event #{event}: #{ids}"
+  callbacks_for_event = callbacks[event]
+  if callbacks_for_event.reject! { |cb| cb.object_id == id }
+    return
+  end
 
+  ids = callbacks_for_event.map(&:object_id)
+  raise Error::WebDriverError, "Callback with ID #{id} does not exist for event #{event}: #{ids}"
+end
+

Pattern 5: Reduce duplication and remove redundant/dead logic by centralizing shared behavior (helpers, base types, shared utilities, linked single-source files) and deleting code paths that are ineffective or already handled elsewhere.

Example code before:

// repeated in many types
class A { string Type => "a"; }
class B { string Type => "b"; }

// redundant filtering already applied upstream
if (event.context == ctx) handler(event);

// unused helper left behind
expr; // no-op

Example code after:

abstract class Typed(string discriminator) { string Type => discriminator; }
class A : Typed("a") {}
class B : Typed("b") {}

// rely on upstream filter
handler(event);

// delete no-op/unused code and link to a single shared source file
Relevant past accepted suggestions:
Suggestion 1:

Remove ineffective window style setting

Remove the ineffective WindowStyle assignment, as it has no effect when UseShellExecute is false. The existing CreateNoWindow property already handles hiding the window.

dotnet/src/webdriver/DriverService.cs [254-260]

 this.driverServiceProcess.StartInfo.UseShellExecute = false;
 this.driverServiceProcess.StartInfo.CreateNoWindow = this.HideCommandPromptWindow;
 
-if (this.HideCommandPromptWindow)
-{
-    this.driverServiceProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
-}
-

Suggestion 2:

Centralize type discriminator logic

Avoid repeating the discriminator property across all records; centralize it in a shared base/helper to prevent drift and reduce boilerplate.

dotnet/src/webdriver/BiDi/Script/LocalValue.cs [284-362]

-public sealed record NumberLocalValue([property: JsonConverter(typeof(SpecialNumberConverter))] double Value) : PrimitiveProtocolLocalValue
+public abstract record TypedLocalValue(string Discriminator) : LocalValue
 {
     [JsonInclude]
-    internal string Type { get; } = "number";
+    internal string Type { get; } = Discriminator;
+}
 
+public abstract record TypedPrimitiveLocalValue(string Discriminator) : PrimitiveProtocolLocalValue
+{
+    [JsonInclude]
+    internal string Type { get; } = Discriminator;
+}
+
+public sealed record NumberLocalValue([property: JsonConverter(typeof(SpecialNumberConverter))] double Value)
+    : TypedPrimitiveLocalValue("number")
+{
     public static explicit operator NumberLocalValue(double n) => new NumberLocalValue(n);
 }
 
-public sealed record StringLocalValue(string Value) : PrimitiveProtocolLocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "string";
-}
+public sealed record StringLocalValue(string Value) : TypedPrimitiveLocalValue("string");
+public sealed record NullLocalValue() : TypedPrimitiveLocalValue("null");
+public sealed record UndefinedLocalValue() : TypedPrimitiveLocalValue("undefined");
+public sealed record BooleanLocalValue(bool Value) : TypedPrimitiveLocalValue("boolean");
+public sealed record BigIntLocalValue(string Value) : TypedPrimitiveLocalValue("bigint");
 
-public sealed record NullLocalValue : PrimitiveProtocolLocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "null";
-}
+public sealed record ChannelLocalValue(ChannelProperties Value) : TypedLocalValue("channel");
+public sealed record ArrayLocalValue(IEnumerable<LocalValue> Value) : TypedLocalValue("array");
+public sealed record DateLocalValue(string Value) : TypedLocalValue("date");
+public sealed record MapLocalValue(IEnumerable<IEnumerable<LocalValue>> Value) : TypedLocalValue("map");
+public sealed record ObjectLocalValue(IEnumerable<IEnumerable<LocalValue>> Value) : TypedLocalValue("object");
+public sealed record RegExpLocalValue(RegExpValue Value) : TypedLocalValue("regexp");
+public sealed record SetLocalValue(IEnumerable<LocalValue> Value) : TypedLocalValue("set");
 
-public sealed record UndefinedLocalValue : PrimitiveProtocolLocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "undefined";
-}
-
-public sealed record BooleanLocalValue(bool Value) : PrimitiveProtocolLocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "boolean";
-}
-
-public sealed record BigIntLocalValue(string Value) : PrimitiveProtocolLocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "bigint";
-}
-
-public sealed record ChannelLocalValue(ChannelProperties Value) : LocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "channel";
-}
-
-public sealed record ArrayLocalValue(IEnumerable<LocalValue> Value) : LocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "array";
-}
-
-public sealed record DateLocalValue(string Value) : LocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "date";
-}
-
-public sealed record MapLocalValue(IEnumerable<IEnumerable<LocalValue>> Value) : LocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "map";
-}
-
-public sealed record ObjectLocalValue(IEnumerable<IEnumerable<LocalValue>> Value) : LocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "object";
-}
-
-public sealed record RegExpLocalValue(RegExpValue Value) : LocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "regexp";
-}
-
-public sealed record SetLocalValue(IEnumerable<LocalValue> Value) : LocalValue
-{
-    [JsonInclude]
-    internal string Type { get; } = "set";
-}
-

Suggestion 3:

Eliminate dead JSON options

Remove the now-unused JsonSerializerOptions method and inline config; prefer the new source-generated contexts to avoid dead code and duplication.

dotnet/src/webdriver/BiDi/BiDi.cs [95-117]

-return new JsonSerializerOptions
-{
-    //PropertyNameCaseInsensitive = true,
-    //PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
-    //DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+// Remove GetJsonOptions() entirely and references to it.
+// Modules now use source-generated contexts (e.g., BrowsingContextJsonSerializerContext.Default)
 
-    // BiDi returns special numbers such as "NaN" as strings
-    // Additionally, -0 is returned as a string "-0"
-    NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString,
-    Converters =
-    {
-        //new BrowsingContextConverter(),
-        //new BrowserUserContextConverter(),
-        //new CollectorConverter(),
-        //new InterceptConverter(),
-        //new HandleConverter(),
-        //new InternalIdConverter(),
-        //new PreloadScriptConverter(),
-        //new RealmConverter(),
-        new DateTimeOffsetConverter(),
-        //new WebExtensionConverter(),
-    }
-};
-

Suggestion 4:

Remove statement with no effect

In the generate_from_json method, remove the line expr which is a statement with no effect and likely a debugging leftover.

py/generate.py [294-312]

 def generate_from_json(self, dict_):
     """Generate the code that creates an instance from a JSON dict named `dict_`."""
     if self.items:
         if self.items.ref:
             py_ref = ref_to_python(self.items.ref)
             expr = f"[{py_ref}.from_json(i) for i in {dict_}['{self.name}']]"
-            expr
         else:
             cons = CdpPrimitiveType.get_constructor(self.items.type, "i")
             expr = f"[{cons} for i in {dict_}['{self.name}']]"
     else:
         if self.ref:
             py_ref = ref_to_python(self.ref)
             expr = f"{py_ref}.from_json({dict_}['{self.name}'])"
         else:
             expr = CdpPrimitiveType.get_constructor(self.type, f"{dict_}['{self.name}']")
     if self.optional:
         expr = f"{expr} if '{self.name}' in {dict_} else None"
     return expr

Suggestion 5:

Avoid code duplication by linking files

Remove the duplicated StringSyntaxAttribute.cs file and instead link to the single source file from the project file to avoid code duplication.

dotnet/src/support/Internal/StringSyntaxAttribute.cs [1-88]

-// <copyright file="StringSyntaxAttribute.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>
+// This file should be deleted.
 
-#if !NET8_0_OR_GREATER
-
-namespace System.Diagnostics.CodeAnalysis;
-
-/// <summary>Specifies the syntax used in a string.</summary>
-[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
-internal sealed class StringSyntaxAttribute : Attribute
-{
-    /// <summary>Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.</summary>
-    /// <param name="syntax">The syntax identifier.</param>
-    public StringSyntaxAttribute(string syntax)
-    {
-        Syntax = syntax;
-        Arguments = [];
-    }
-
-    /// <summary>Initializes the <see cref="StringSyntaxAttribute"/> with the identifier of the syntax used.</summary>
-    /// <param name="syntax">The syntax identifier.</param>
-    /// <param name="arguments">Optional arguments associated with the specific syntax employed.</param>
-    public StringSyntaxAttribute(string syntax, params object?[] arguments)
-    {
-        Syntax = syntax;
-        Arguments = arguments;
-    }
-
-    /// <summary>Gets the identifier of the syntax used.</summary>
-    public string Syntax { get; }
-
-    /// <summary>Optional arguments associated with the specific syntax employed.</summary>
-    public object?[] Arguments { get; }
-
-    /// <summary>The syntax identifier for strings containing composite formats for string formatting.</summary>
-    public const string CompositeFormat = nameof(CompositeFormat);
-
-    /// <summary>The syntax identifier for strings containing date format specifiers.</summary>
-    public const string DateOnlyFormat = nameof(DateOnlyFormat);
-
-    /// <summary>The syntax identifier for strings containing date and time format specifiers.</summary>
-    public const string DateTimeFormat = nameof(DateTimeFormat);
-
-    /// <summary>The syntax identifier for strings containing <see cref="Enum"/> format specifiers.</summary>
-    public const string EnumFormat = nameof(EnumFormat);
-
-    /// <summary>The syntax identifier for strings containing <see cref="Guid"/> format specifiers.</summary>
-    public const string GuidFormat = nameof(GuidFormat);
-
-    /// <summary>The syntax identifier for strings containing JavaScript Object Notation (JSON).</summary>
-    public const string Json = nameof(Json);
-
-    /// <summary>The syntax identifier for strings containing numeric format specifiers.</summary>
-    public const string NumericFormat = nameof(NumericFormat);
-
-    /// <summary>The syntax identifier for strings containing regular expressions.</summary>
-    public const string Regex = nameof(Regex);
-
-    /// <summary>The syntax identifier for strings containing time format specifiers.</summary>
-    public const string TimeOnlyFormat = nameof(TimeOnlyFormat);
-
-    /// <summary>The syntax identifier for strings containing <see cref="TimeSpan"/> format specifiers.</summary>
-    public const string TimeSpanFormat = nameof(TimeSpanFormat);
-
-    /// <summary>The syntax identifier for strings containing URIs.</summary>
-    public const string Uri = nameof(Uri);
-
-    /// <summary>The syntax identifier for strings containing XML.</summary>
-    public const string Xml = nameof(Xml);
-}
-
-#endif
-

Suggestion 6:

Remove redundant client-side context filtering

Remove the redundant client-side context check in OnEntryAddedAsync methods. The event subscription is already filtered by context at the protocol level, making the if statement unnecessary.

dotnet/src/webdriver/BiDi/BrowsingContext/BrowsingContextLogModule.cs [28-48]

 public Task<Subscription> OnEntryAddedAsync(Func<Log.LogEntry, Task> handler, ContextSubscriptionOptions? options = null)
 {
-    return logModule.OnEntryAddedAsync(async args =>
-    {
-        if (args.Source.Context?.Equals(context) is true)
-        {
-            await handler(args).ConfigureAwait(false);
-        }
-    }, options.WithContext(context));
+    return logModule.OnEntryAddedAsync(handler, options.WithContext(context));
 }
 
 public Task<Subscription> OnEntryAddedAsync(Action<Log.LogEntry> handler, ContextSubscriptionOptions? options = null)
 {
-    return logModule.OnEntryAddedAsync(args =>
-    {
-        if (args.Source.Context?.Equals(context) is true)
-        {
-            handler(args);
-        }
-    }, options.WithContext(context));
+    return logModule.OnEntryAddedAsync(handler, options.WithContext(context));
 }

Suggestion 7:

Refactor duplicated code into a utility method

Extract the duplicated configureHeartbeat method from BoundZmqEventBus and UnboundZmqEventBus into a static helper method in a new utility class to avoid code repetition and improve maintainability.

java/src/org/openqa/selenium/events/zeromq/BoundZmqEventBus.java [84-97]

-private void configureHeartbeat(ZMQ.Socket socket, Duration heartbeatPeriod, String socketType) {
-  if (heartbeatPeriod != null && !heartbeatPeriod.isZero() && !heartbeatPeriod.isNegative()) {
-    int heartbeatIvl = (int) heartbeatPeriod.toMillis();
-    socket.setHeartbeatIvl(heartbeatIvl);
-    // Set heartbeat timeout to 3x the interval
-    socket.setHeartbeatTimeout(heartbeatIvl * 3);
-    // Set heartbeat TTL to 6x the interval
-    socket.setHeartbeatTtl(heartbeatIvl * 6);
-    LOG.info(
-        String.format(
-            "Event bus %s socket heartbeat configured: interval=%dms, timeout=%dms, ttl=%dms",
-            socketType, heartbeatIvl, heartbeatIvl * 3, heartbeatIvl * 6));
+// In a new file: java/src/org/openqa/selenium/events/zeromq/ZmqUtils.java
+package org.openqa.selenium.events.zeromq;
+
+import java.time.Duration;
+import java.util.logging.Logger;
+import org.zeromq.ZMQ;
+
+class ZmqUtils {
+  private static final Logger LOG = Logger.getLogger(ZmqUtils.class.getName());
+
+  private ZmqUtils() {
+    // Utility class
+  }
+
+  static void configureHeartbeat(ZMQ.Socket socket, Duration heartbeatPeriod, String socketType) {
+    if (heartbeatPeriod != null && !heartbeatPeriod.isZero() && !heartbeatPeriod.isNegative()) {
+      long periodMillis = heartbeatPeriod.toMillis();
+      if (periodMillis > Integer.MAX_VALUE) {
+        LOG.warning(
+            String.format(
+                "Heartbeat period %dms is too large. Capping at %dms.",
+                periodMillis, Integer.MAX_VALUE));
+        periodMillis = Integer.MAX_VALUE;
+      }
+      int heartbeatIvl = (int) periodMillis;
+      socket.setHeartbeatIvl(heartbeatIvl);
+      // Set heartbeat timeout to 3x the interval
+      socket.setHeartbeatTimeout(heartbeatIvl * 3);
+      // Set heartbeat TTL to 6x the interval
+      socket.setHeartbeatTtl(heartbeatIvl * 6);
+      LOG.info(
+          String.format(
+              "Event bus %s socket heartbeat configured: interval=%dms, timeout=%dms, ttl=%dms",
+              socketType, heartbeatIvl, heartbeatIvl * 3, heartbeatIvl * 6));
+    }
   }
 }
 
+// In BoundZmqEventBus.java, replace the configureHeartbeat method with:
+private void configureHeartbeat(ZMQ.Socket socket, Duration heartbeatPeriod, String socketType) {
+  ZmqUtils.configureHeartbeat(socket, heartbeatPeriod, socketType);
+}
+

Suggestion 8:

Remove redundant and unused code

Remove the redundant file javascript/grid-ui/src/hooks/useTheme.tsx. Its logic is already implemented in javascript/grid-ui/src/contexts/ThemeContext.tsx, and it is not used by the application.

javascript/grid-ui/src/hooks/useTheme.tsx [1-61]

-// 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.
+// This file should be deleted.
 
-import { useState, useEffect } from 'react'
-import { lightTheme, darkTheme } from '../theme/themes'
-
-type ThemeMode = 'light' | 'dark' | 'system'
-
-export const useTheme = () => {
-  const [themeMode, setThemeMode] = useState<ThemeMode>('system')
-  const [systemPrefersDark, setSystemPrefersDark] = useState(false)
-
-  useEffect(() => {
-    if (typeof window !== 'undefined') {
-      const saved = localStorage.getItem('theme-mode') as ThemeMode
-      if (saved) setThemeMode(saved)
-      setSystemPrefersDark(window.matchMedia('(prefers-color-scheme: dark)').matches)
-    }
-  }, [])
-
-
-
-  useEffect(() => {
-    if (typeof window !== 'undefined') {
-      localStorage.setItem('theme-mode', themeMode)
-    }
-  }, [themeMode])
-
-  useEffect(() => {
-    if (typeof window !== 'undefined') {
-      const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
-      const handler = (e: MediaQueryListEvent) => setSystemPrefersDark(e.matches)
-      mediaQuery.addEventListener('change', handler)
-      return () => mediaQuery.removeEventListener('change', handler)
-    }
-  }, [])
-
-  const isDark = themeMode === 'dark' || (themeMode === 'system' && systemPrefersDark)
-  const currentTheme = isDark ? darkTheme : lightTheme
-
-  return {
-    themeMode,
-    setThemeMode,
-    currentTheme,
-    isDark
-  }
-}
-

[Auto-generated best practices - 2025-12-28]

Clone this wiki locally