[java][bidi]: add `DownloadInfo` class and test for `onDownloadWillBegin` by navin772 · Pull Request #16267 · SeleniumHQ/selenium

🎫 Ticket compliance analysis 🔶

1234 - Partially compliant

Compliant requirements:

  • None

Non-compliant requirements:

  • Fix regression for JS-in-href click behavior in Firefox.
  • Add tests validating alert/JS execution on click.

Requires further human verification:

  • None

5678 - Partially compliant

Compliant requirements:

  • None

Non-compliant requirements:

  • Resolve ConnectFailure across multiple ChromeDriver instantiations.
  • Add tests or documentation to verify multiple instance stability.

Requires further human verification:

  • None
⚡ Recommended focus areas for review
Null Handling

Validate that all fields parsed from JSON, especially 'timestamp' and IDs, are present and non-null. Currently defaults (e.g., 0 for long) are used without validation, which could mask malformed events.

public static DownloadInfo fromJson(JsonInput input) {
  String browsingContextId = null;
  String navigationId = null;
  long timestamp = 0;
  String url = null;
  String suggestedFilename = null;

  input.beginObject();
  while (input.hasNext()) {
    switch (input.nextName()) {
      case "context":
        browsingContextId = input.read(String.class);
        break;

      case "navigation":
        navigationId = input.read(String.class);
        break;

      case "timestamp":
        timestamp = input.read(Long.class);
        break;

      case "url":
        url = input.read(String.class);
        break;

      case "suggestedFilename":
        suggestedFilename = input.read(String.class);
        break;

      default:
        input.skipValue();
        break;
    }
  }

  input.endObject();

  return new DownloadInfo(browsingContextId, navigationId, timestamp, url, suggestedFilename);
}
Listener Cleanup

Ensure no listeners remain when specific context-scoped listeners are added; verify that 'downloadWillBeginEvent' respects context filters like other navigation events and is always cleared in close().

public void onBrowsingContextLoaded(Consumer<NavigationInfo> consumer) {
  addNavigationEventListener("browsingContext.load", consumer);
}

public void onDownloadWillBegin(Consumer<DownloadInfo> consumer) {
  if (browsingContextIds.isEmpty()) {
    this.bidi.addListener(downloadWillBeginEvent, consumer);
  } else {
    this.bidi.addListener(browsingContextIds, downloadWillBeginEvent, consumer);
  }
}

public void onNavigationAborted(Consumer<NavigationInfo> consumer) {
  addNavigationEventListener("browsingContext.navigationAborted", consumer);
}

public void onNavigationFailed(Consumer<NavigationInfo> consumer) {
  addNavigationEventListener("browsingContext.navigationFailed", consumer);
}

public void onNavigationCommitted(Consumer<NavigationInfo> consumer) {
  addNavigationEventListener("browsingContext.navigationCommitted", consumer);
}

public void onUserPromptClosed(Consumer<UserPromptClosed> consumer) {
  if (browsingContextIds.isEmpty()) {
    this.bidi.addListener(userPromptClosed, consumer);
  } else {
    this.bidi.addListener(browsingContextIds, userPromptClosed, consumer);
  }
}

public void onUserPromptOpened(Consumer<UserPromptOpened> consumer) {
  if (browsingContextIds.isEmpty()) {
    this.bidi.addListener(userPromptOpened, consumer);
  } else {
    this.bidi.addListener(browsingContextIds, userPromptOpened, consumer);
  }
}

public void onHistoryUpdated(Consumer<HistoryUpdated> consumer) {
  if (browsingContextIds.isEmpty()) {
    this.bidi.addListener(historyUpdated, consumer);
  } else {
    this.bidi.addListener(browsingContextIds, historyUpdated, consumer);
  }
}

private void addNavigationEventListener(String name, Consumer<NavigationInfo> consumer) {
  Event<NavigationInfo> navigationEvent = new Event<>(name, navigationInfoMapper);

  navigationEventSet.add(navigationEvent);

  if (browsingContextIds.isEmpty()) {
    this.bidi.addListener(navigationEvent, consumer);
  } else {
    this.bidi.addListener(browsingContextIds, navigationEvent, consumer);
  }
}

@Override
public void close() {
  this.bidi.clearListener(browsingContextCreated);
  this.bidi.clearListener(browsingContextDestroyed);
  this.bidi.clearListener(userPromptOpened);
  this.bidi.clearListener(userPromptClosed);
  this.bidi.clearListener(historyUpdated);
  this.bidi.clearListener(downloadWillBeginEvent);

  navigationEventSet.forEach(this.bidi::clearListener);