Skip to content

[js] Add serialization and domain layer - #17927

Merged
pujagani merged 5 commits into
SeleniumHQ:trunkfrom
pujagani:bidi-domain-js
Aug 24, 2026
Merged

[js] Add serialization and domain layer#17927
pujagani merged 5 commits into
SeleniumHQ:trunkfrom
pujagani:bidi-domain-js

Conversation

@pujagani

Copy link
Copy Markdown
Contributor

🔗 Related Issues

Add Domain and Serialization class, foundation for CDDL generation.

💥 What does this PR do?

Introduces Domain base class (modules will be built on top of this for generator) and bunch of serialization base classes (
defineRecord/defineEnum/defineUnion etc) in alignment with low-level behavioral ADR.

🔧 Implementation Notes

🤖 AI assistance

  • No substantial AI assistance used
  • AI assisted (complete below)
    • Tool(s):
    • What was generated:
    • I reviewed all AI output and can explain the change

💡 Additional Considerations

🔄 Types of changes

  • New feature (non-breaking change which adds functionality and tests!)

@selenium-ci selenium-ci added C-nodejs JavaScript Bindings B-build Includes scripting, bazel and CI integrations B-devtools Includes everything BiDi or Chrome DevTools related labels Aug 18, 2026
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Add JavaScript BiDi domain and serialization foundation

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Adds a guarded base class for generated WebDriver BiDi domains.
• Introduces schema-driven records, enums, aliases, unions, and runtime wire validation.
• Defines inbound/outbound contracts with comprehensive serialization and security tests.
Diagram

graph TD
  G["Generated Domains"] --> D["Domain Base"] --> B["BiDi Connection"]
  G --> S["Serialization Types"] --> R["Type Registry"] --> V["Wire Validation"] --> B
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Generate standalone serializers per domain
  • ➕ Eliminates the global runtime registry
  • ➕ Allows generated code to inline domain-specific validation
  • ➖ Duplicates validation logic across generated modules
  • ➖ Increases generated output and generator complexity
  • ➖ Makes consistent inbound and outbound behavior harder to maintain
2. Use JSON Schema with Ajv
  • ➕ Uses a mature validation engine
  • ➕ Provides broad schema validation capabilities
  • ➖ Requires translating BiDi CDDL semantics into JSON Schema
  • ➖ Adds a runtime dependency and bundle overhead
  • ➖ Does not naturally model directional extras or typed union instances

Recommendation: Retain the shared schema-aware runtime introduced by this PR. It centralizes wire-contract behavior while keeping generated domain modules small, and deferred registry resolution directly supports cross-domain, forward, and circular references. Standalone generation or Ajv would add duplication or impedance without clear benefit for the BiDi-specific semantics.

Files changed (14) +1309 / -0

Enhancement (9) +631 / -0
domain.d.tsDeclare the generated BiDi domain API +37/-0

Declare the generated BiDi domain API

• Defines typed event descriptors, the guarded domain construction token, command dispatch, and callback subscription methods.

javascript/selenium-webdriver/bidi/domain.d.ts

domain.jsImplement the shared BiDi domain runtime +76/-0

Implement the shared BiDi domain runtime

• Adds guarded domain construction, shared connection acquisition, remote-error handling, and typed event payload parsing before callback delivery.

javascript/selenium-webdriver/bidi/domain.js

enum.d.tsDeclare schema enum definitions +23/-0

Declare schema enum definitions

• Adds the typed enum entry contract and defineEnum factory declaration.

javascript/selenium-webdriver/bidi/serialization/enum.d.ts

enum.jsImplement registered schema enums +31/-0

Implement registered schema enums

• Creates set-backed enum membership checks and registers enum definitions for reference validation.

javascript/selenium-webdriver/bidi/serialization/enum.js

record.d.tsDeclare record schemas and validation types +58/-0

Declare record schemas and validation types

• Defines schema type nodes, field metadata, extensibility options, immutable record classes, aliases, and validation errors.

javascript/selenium-webdriver/bidi/serialization/record.d.ts

record.jsImplement directional record validation +258/-0

Implement directional record validation

• Adds immutable schema records with strict outbound validation and tolerant inbound handling of undeclared fields. Supports primitives, constants, enums, collections, references, aliases, unions, nullable values, and prototype-safe extensible fields.

javascript/selenium-webdriver/bidi/serialization/record.js

registry.jsAdd deferred schema type resolution +34/-0

Add deferred schema type resolution

• Introduces a shared name-based type registry so generated types can resolve forward, circular, and cross-domain references during validation.

javascript/selenium-webdriver/bidi/serialization/registry.js

union.d.tsDeclare schema union definitions +27/-0

Declare schema union definitions

• Defines union options and typed outbound build and inbound parsing operations.

javascript/selenium-webdriver/bidi/serialization/union.d.ts

union.jsImplement discriminated and structural unions +87/-0

Implement discriminated and structural unions

• Selects union variants by discriminator or ordered required-key matching, then delegates to the selected record's directional validation path.

javascript/selenium-webdriver/bidi/serialization/union.js

Tests (4) +673 / -0
domain_test.jsTest domain callbacks and construction safeguards +98/-0

Test domain callbacks and construction safeguards

• Verifies typed and untyped event dispatch, callback removal, token enforcement, and private transport encapsulation.

javascript/selenium-webdriver/test/bidi/domain_test.js

record_test.jsTest record validation and extensibility +191/-0

Test record validation and extensibility

• Covers required fields, enum and integer validation, nullability, immutability, inbound warnings, extensible extras, and prototype-pollution protection.

javascript/selenium-webdriver/test/bidi/serialization/record_test.js

union_test.jsTest union variant selection +117/-0

Test union variant selection

• Exercises outbound and inbound dispatch for discriminated and structural unions, including unknown variants and extensible records.

javascript/selenium-webdriver/test/bidi/serialization/union_test.js

wire_contract_test.jsCodify BiDi wire-contract guarantees +267/-0

Codify BiDi wire-contract guarantees

• Tests typed representation, field-name mapping, numeric fidelity, directional validation, extensibility behavior, undeclared-field handling, and remote error precedence.

javascript/selenium-webdriver/test/bidi/serialization/wire_contract_test.js

Other (1) +5 / -0
BUILD.bazelPackage serialization modules and register their tests +5/-0

Package serialization modules and register their tests

• Includes the new BiDi serialization directory in the JavaScript library and adds domain, record, union, and wire-contract suites to the small-test target.

javascript/selenium-webdriver/BUILD.bazel

@qodo-code-review

qodo-code-review Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Typed event parse can crash ✗ Dismissed 🐞 Bug ☼ Reliability ⭐ New
Description
When a descriptor has a type, the event listener calls descriptor.type.fromWire(params) without a
try/catch, so an invalid remote payload will throw during event dispatch. Because the BiDi WebSocket
message handler calls emit() without guarding listener exceptions, this error can bubble out of
the 'message' event and crash the process.
Code

javascript/selenium-webdriver/bidi/domain.js[R85-86]

+    await this.#bidi.subscribe(descriptor.method)
+    this.#bidi.on(descriptor.method, dispatch)
Evidence
The new listener registration uses a dispatch function that calls fromWire() directly.
fromWire() is designed to throw on invalid payloads. The BiDi connection’s WebSocket 'message'
handler emits protocol events via emit() without any try/catch, so any exception from a listener
will escape that handler path.

javascript/selenium-webdriver/bidi/domain.js[83-86]
javascript/selenium-webdriver/bidi/serialization/record.js[256-273]
javascript/selenium-webdriver/bidi/index.js[62-106]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Domain.addCallback()` wraps the handler with `descriptor.type.fromWire(params)` but does not catch exceptions. If `fromWire()` rejects an invalid wire payload (expected behavior), the exception is thrown from inside the BiDi connection’s WebSocket `'message'` handler during `EventEmitter.emit()`, which is unhandled in this code path.

### Issue Context
- `Record.fromWire()` throws `ValidationError` on invalid payloads.
- The BiDi connection emits protocol events via `this.emit(payload.method, payload.params)` inside the ws `'message'` callback and does not wrap `emit()` in a try/catch.

### Fix Focus Areas
- javascript/selenium-webdriver/bidi/domain.js[83-86]
- javascript/selenium-webdriver/bidi/index.js[62-106]

### Implementation sketch
- Wrap the typed dispatch in a try/catch:
 - On success: call the user handler.
 - On failure: do *not* throw synchronously.
   - Option A (preferred): `process.emitWarning(...)` with event method + error message and a dedicated warning type (e.g., `BiDiSchemaWarning`).
   - Option B: if `this.#bidi.listenerCount('error') > 0`, emit an `'error'` event on the connection; otherwise fallback to `process.emitWarning(...)` (avoid emitting `'error'` with no listeners).
- Add a unit test that emits an invalid payload for a typed descriptor and asserts:
 - the process does not throw synchronously
 - the user handler is not invoked
 - a warning (or error event) is produced.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unsubscribe breaks other listeners ✗ Dismissed 🐞 Bug ≡ Correctness ⭐ New
Description
Domain#addCallback() returns a per-handler unsubscribe handle, but calling it always sends a remote
session.unsubscribe for the event method, which will stop events for any other still-registered
local listeners on the same BiDi connection. This causes silent event loss (the other listener
remains attached locally but will never fire once the remote stops sending).
Code

javascript/selenium-webdriver/bidi/domain.js[R88-91]

+      unsubscribe: async () => {
+        this.#bidi.off(descriptor.method, dispatch)
+        await this.#bidi.unsubscribe(descriptor.method)
+      },
Evidence
The new implementation always unsubscribes remotely inside the per-handler unsubscribe closure. The
underlying BiDi connection’s unsubscribe() sends session.unsubscribe for the method, which stops
the remote from sending any more events for that method (connection-wide). The new test only
verifies local listener removal, so it does not catch the real-world behavior where the remote stops
sending for all listeners after the first unsubscribe.

javascript/selenium-webdriver/bidi/domain.js[83-91]
javascript/selenium-webdriver/bidi/index.js[237-273]
javascript/selenium-webdriver/bidi/index.js[281-312]
javascript/selenium-webdriver/test/bidi/domain_test.js[77-103]
javascript/selenium-webdriver/test/bidi/domain_test.js[119-134]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Domain.addCallback()` always calls `bidi.subscribe(method)` and each returned `unsubscribe()` always calls `bidi.unsubscribe(method)`. With two handlers for the same event method, unsubscribing one handler unsubscribes the *remote* event stream for the whole connection, so the other handler will stop receiving events.

### Issue Context
This is inconsistent with the returned handle being per-handler, and is masked by the unit test’s fake transport (which continues to `emit()` events even after `unsubscribe()` is called).

### Fix Focus Areas
- javascript/selenium-webdriver/bidi/domain.js[68-93]
- javascript/selenium-webdriver/bidi/domain.d.ts[41-55]
- javascript/selenium-webdriver/test/bidi/domain_test.js[77-134]

### Implementation sketch
- Maintain a per-connection, per-method refcount (e.g., `WeakMap<bidi, Map<method, {count:number}>>`).
- In `addCallback()`:
 - Attach the local listener.
 - If refcount transitions 0→1, call `await bidi.subscribe(method)`.
 - If subscribe fails, detach the local listener and roll back refcount.
- In `unsubscribe()`:
 - Detach the local listener.
 - If refcount transitions 1→0, call `await bidi.unsubscribe(method)`.
- Update the d.ts docstring to reflect ref-counting semantics.
- Update/extend tests to assert that multiple listeners do not cause multiple remote subscribes, and that only the final unsubscribe triggers the remote unsubscribe.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. toJSON __proto__ hazard ✓ Resolved 🐞 Bug ⛨ Security
Description
Record.toJSON() serializes fields into a plain {} and assigns keys directly, so an extensible
record containing an extra "__proto__" field (which the code explicitly preserves as data) will
instead mutate the serialized object's prototype and drop/alter the intended wire representation.
This reintroduces the CWE-1321 class of bug during serialization even though construction/parsing
guarded against it.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R304-307]

+      const wire = {}
+      for (const key of Object.keys(this)) {
+        wire[byName.get(key) ?? key] = this[key] // extras have no JS-name mapping — already wire-keyed
+      }
Evidence
The record implementation explicitly treats __proto__ as a dangerous key and preserves it as data
on instances, but toJSON reconstructs a plain object and uses bracket assignment for all keys, which
is exactly the pattern they avoided elsewhere. The test suite already encodes the expectation that
__proto__ is preserved as data on records, so serialization should not break that guarantee.

javascript/selenium-webdriver/bidi/serialization/record.js[303-308]
javascript/selenium-webdriver/bidi/serialization/record.js[232-241]
javascript/selenium-webdriver/test/bidi/serialization/record_test.js[221-239]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`toJSON()` uses `const wire = {}` and then `wire[someKey] = ...`. If `someKey` is `"__proto__"` (possible via extensible extras, which are explicitly allowed and preserved), this will mutate `wire`'s prototype rather than creating a data property. The resulting serialized payload can be incorrect and the hardening against `__proto__` is effectively bypassed at the point of wire serialization.

### Issue Context
This module already goes out of its way to prevent `__proto__` from hijacking record instances (constructor/fromWire use `Object.defineProperty`). The same protection needs to exist in toJSON.

### Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[303-309]

### Suggested fix
- Change `wire` to `Object.create(null)` (preferred) so `"__proto__"` is a normal key.
- Alternatively, define properties with `Object.defineProperty(wire, key, { value, enumerable: true, ... })`.
- Add a focused test ensuring `JSON.stringify(recordWith__proto__)` preserves the `"__proto__"` key/value as data.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View action required (7)
4. Event subscription race ✓ Resolved 🐞 Bug ☼ Reliability
Description
Domain.addCallback() awaits remote subscribe() before attaching the local listener, so events sent
immediately after subscription can be emitted by the BiDi connection before the handler is
registered and get dropped. The async unsubscribe() path can also interleave with a concurrent
addCallback() and leave the connection unsubscribed while a listener exists.
Code

javascript/selenium-webdriver/bidi/domain.js[R88-91]

+    if (this.#bidi.listenerCount(descriptor.method) === 0) {
+      await this.#bidi.subscribe(descriptor.method)
+    }
+    this.#bidi.on(descriptor.method, dispatch)
Evidence
The new addCallback implementation awaits remote subscription before registering the local handler.
The BiDi transport re-emits events immediately upon receiving frames, so any event arriving after
subscribe completes but before on() is called will be emitted with zero listeners and dropped.
Additionally, subscribe/unsubscribe are async BiDi commands, so concurrent add/remove can reorder
those commands relative to local listenerCount checks.

javascript/selenium-webdriver/bidi/domain.js[68-99]
javascript/selenium-webdriver/bidi/index.js[82-106]
javascript/selenium-webdriver/bidi/index.js[231-273]
javascript/selenium-webdriver/bidi/index.js[275-312]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`Domain.addCallback()` currently does `await subscribe()` before `on()`, and `unsubscribe()` does `off()` then `await unsubscribe()`. Because these steps are separated by `await`, events can be missed (subscribe→emit→on window) and concurrent add/remove can interleave to produce a remotely-unsubscribed connection even though local listeners exist.

### Issue Context
The underlying BiDi connection emits incoming events via `EventEmitter.emit(method, params)` and remote subscription is performed by sending `session.subscribe` / `session.unsubscribe` commands.

### Fix Focus Areas
- javascript/selenium-webdriver/bidi/domain.js[86-99]

### Suggested fix
- Attach the local listener before awaiting remote subscribe, and if subscribe fails, remove the listener in a `catch`/`finally` to avoid leaking handlers.
- Add a per-method subscription mutex/queue (or a small refcount state machine on the connection) so subscribe/unsubscribe transitions are serialized and cannot interleave incorrectly across concurrent callers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Unsafe map key assignment ✓ Resolved 🐞 Bug ⛨ Security
Description
validateValue() builds map-typed results with a normal {} and assigns arbitrary wire keys
directly, so a payload key like "__proto__" will be treated as the magic accessor and can change the
returned object's prototype instead of being preserved as data. This undermines the library's
explicit CWE-1321 hardening elsewhere and can corrupt validated outputs.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R90-94]

+    const result = {}
+    for (const [key, entry] of Object.entries(value)) {
+      result[key] = validateValue(typeNode.map, entry, `${path}.${key}`, direction)
+    }
+    return Object.freeze(result)
Evidence
The map branch copies untrusted keys into a plain object using bracket assignment, while the record
constructor/fromWire explicitly documents and mitigates the __proto__ hazard using defineProperty.
This leaves an inconsistent and exploitable gap for map-typed fields.

javascript/selenium-webdriver/bidi/serialization/record.js[86-95]
javascript/selenium-webdriver/bidi/serialization/record.js[232-241]
javascript/selenium-webdriver/bidi/serialization/record.js[279-287]
javascript/selenium-webdriver/test/bidi/serialization/record_test.js[221-239]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`validateValue()`'s `typeNode.map` branch constructs `result = {}` and then does `result[key] = ...` for attacker-controlled keys. If `key` is `"__proto__"`, this mutates `result`'s prototype rather than creating a data property, violating the intended behavior and potentially enabling prototype-based confusion in downstream consumers.

### Issue Context
The same module explicitly calls out CWE-1321 and uses `Object.defineProperty` to prevent `__proto__` from hijacking record instances, but `map` handling does not apply a similar protection.

### Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[86-95]

### Suggested fix
- Create map outputs with `Object.create(null)` (so `__proto__` is not magical) and keep using assignment, or use `Object.defineProperty(result, key, {value, enumerable:true, ...})`.
- Consider applying the same null-prototype approach to other places where untrusted keys are copied into plain objects.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Nested parsing result discarded ✓ Resolved 🐞 Bug ≡ Correctness
Description
Nested record and union references are parsed only for validation, after which fromWire() assigns
the original raw object to the parent. Nested fields therefore remain plain mutable objects and
retain undeclared properties that their nested fromWire() call had dropped.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R103-106]

+      if (direction === 'inbound') {
+        referenced.RecordClass.fromWire(value)
+      } else {
+        new referenced.RecordClass(value)
Evidence
The referenced fromWire() methods return typed sanitized instances, but their return values are
ignored and line 213 stores the original payload value instead.

javascript/selenium-webdriver/bidi/serialization/record.js[96-117]
javascript/selenium-webdriver/bidi/serialization/record.js[203-214]
javascript/selenium-webdriver/bidi/serialization/union.js[70-80]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Nested record and union parsing results are discarded, leaving raw wire objects in otherwise typed parent records.

## Issue Context
`validateValue()` currently returns no transformed value. Refactor validation/parsing so inbound nested refs return and assign the `fromWire()` result, while preserving outbound behavior.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[96-117]
- javascript/selenium-webdriver/bidi/serialization/record.js[203-214]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Inline enums bypass validation ✓ Resolved 🐞 Bug ≡ Correctness
Description
validateValue() returns after checking an inline enum's primitive, so it never checks the same
node's enum values. Projected inline enums therefore accept any value of the correct primitive
type rather than only their declared literals.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R34-44]

+  if (typeNode.primitive !== undefined) {
+    const expected = { string: 'string', integer: 'number', number: 'number', boolean: 'boolean' }[typeNode.primitive]
+    if (expected && typeof value !== expected) {
+      throw new ValidationError(`${path}: expected ${typeNode.primitive}, got ${typeof value}`)
+    }
+    // `number` admits any JSON number; `integer` rejects a fractional value
+    // (5.7) while still accepting one written 5.0 (Number.isInteger(5.0) is true).
+    if (typeNode.primitive === 'integer' && !Number.isInteger(value)) {
+      throw new ValidationError(`${path}: expected an integer, got ${value}`)
+    }
+    return
Evidence
The projector creates {enum: values, primitive: ...} nodes, while the validator's earlier
primitive branch returns before reaching its enum branch.

javascript/selenium-webdriver/project_bidi_schema.mjs[132-140]
javascript/selenium-webdriver/bidi/serialization/record.js[34-63]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Inline enum nodes contain both `primitive` and `enum`, but primitive validation returns before validating the allowed literals.

## Issue Context
`project_bidi_schema.mjs` deliberately emits both properties so bindings can enforce the primitive and closed vocabulary.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[34-63]
- javascript/selenium-webdriver/project_bidi_schema.mjs[132-140]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Validated collections remain mutable ✓ Resolved 🐞 Bug ☼ Reliability
Description
Record construction stores caller-owned arrays and objects directly and only freezes the outer
instance. A caller can mutate a validated list or map afterward, including inserting schema-invalid
values that are then serialized to the wire.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R165-168]

+        const value = data[field.wire]
+        validateValue(field.type, value, `${name}.${field.wire}`, 'outbound')
+        this[field.name] = value
+      }
Evidence
List and map validation only iterates their contents, and both constructors retain the original
value reference before freezing only the record object.

javascript/selenium-webdriver/bidi/serialization/record.js[65-80]
javascript/selenium-webdriver/bidi/serialization/record.js[165-187]
javascript/selenium-webdriver/test/bidi/serialization/record_test.js[76-81]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Validated arrays, maps, and nested objects remain mutable after a record is frozen, allowing post-validation corruption.

## Issue Context
The outer `Object.freeze()` does not freeze or copy values assigned from input data. Store immutable validated copies or deeply freeze the supported value graph.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[65-80]
- javascript/selenium-webdriver/bidi/serialization/record.js[158-187]
- javascript/selenium-webdriver/bidi/serialization/record.js[203-237]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Inline records skip validation ✓ Resolved 🐞 Bug ≡ Correctness
Description
The schema projector emits inline field types as {record: [...]}, but validateValue() has no
record branch and silently accepts them. Missing required members, invalid member types, and
undeclared members in inline records consequently pass both inbound and outbound validation.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R128-140]

+  if (typeNode.union !== undefined) {
+    const errors = []
+    for (const variant of typeNode.union) {
+      try {
+        validateValue(variant, value, path, direction)
+        return
+      } catch (err) {
+        errors.push(err.message)
+      }
+    }
+    throw new ValidationError(`${path}: value did not match any variant (${errors.join('; ')})`)
+  }
+}
Evidence
projectEntry() explicitly returns a record node for an inline named group, but the new TypeNode
declaration and runtime validator recognize no such property.

javascript/selenium-webdriver/project_bidi_schema.mjs[195-204]
javascript/selenium-webdriver/bidi/serialization/record.d.ts[19-36]
javascript/selenium-webdriver/bidi/serialization/record.js[128-140]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Inline record TypeNodes emitted by the schema projector fall through `validateValue()` without any validation.

## Issue Context
Inline records contain projected `FieldSpec` entries and need the same directional required-field, extra-field, and nested-value handling as named records. Add the missing TypeScript representation as well.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[28-140]
- javascript/selenium-webdriver/bidi/serialization/record.d.ts[19-36]
- javascript/selenium-webdriver/project_bidi_schema.mjs[195-204]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Callback transport methods missing ✓ Resolved 🐞 Bug ≡ Correctness
Description
Domain.addCallback() and removeCallback() invoke methods that the real BiDi connection does not
implement, so every generated event registration or removal fails with a TypeError. The connection
is an EventEmitter exposing protocol events through on/off, while its subscription methods are
named subscribe and unsubscribe.
Code

javascript/selenium-webdriver/bidi/domain.js[R66-72]

+  async addCallback(descriptor, handler) {
+    const dispatch = descriptor.type === undefined ? handler : (params) => handler(descriptor.type.fromWire(params))
+    return this.#bidi.addCallback(descriptor.method, dispatch)
+  }
+
+  async removeCallback(subscriptionId) {
+    return this.#bidi.removeCallback(subscriptionId)
Evidence
The domain obtains the Index transport through getBidiConnection; that class emits incoming
event methods but defines no callback registration or removal methods matching these calls.

javascript/selenium-webdriver/bidi/domain.js[54-68]
javascript/selenium-webdriver/lib/bidi_connection.js[39-56]
javascript/selenium-webdriver/bidi/index.js[82-105]
javascript/selenium-webdriver/bidi/index.js[231-312]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Domain.addCallback()` and `removeCallback()` delegate to nonexistent methods on the real BiDi connection, causing event APIs to fail at runtime.

## Issue Context
`getBidiConnection()` returns `bidi/index.js`, which emits events by protocol method name and implements `subscribe()`/`unsubscribe()`, but not `addCallback()`/`removeCallback()`.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/domain.js[66-72]
- javascript/selenium-webdriver/bidi/index.js[82-105]
- javascript/selenium-webdriver/bidi/index.js[231-312]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

11. Subscription queue map grows ✓ Resolved 🐞 Bug ☼ Reliability
Description
queueSubscriptionChange() stores a Promise chain in a per-connection Map keyed by event method, but
never removes entries, so a long-lived BiDi connection can accumulate an unbounded number of method
keys and retained Promise chains. Since event methods are caller-provided strings, repeated/variable
subscriptions can steadily increase memory usage over the lifetime of the connection.
Code

javascript/selenium-webdriver/bidi/domain.js[R61-66]

+  const previous = methods.get(method) ?? Promise.resolve()
+  const next = previous.then(change, change) // run `change` next regardless of a prior failure
+  methods.set(
+    method,
+    next.catch(() => {}),
+  ) // ...but don't let that failure jam the queue for later callers
Evidence
The new implementation introduces a module-level WeakMap of per-connection Maps, and each call
enqueues by writing methods.set(method, next.catch(() => {})) with no corresponding delete().
Because descriptor.method is a plain string coming from the public event()/addCallback() API,
the set of method keys is not guaranteed to remain small on a long-lived connection.

javascript/selenium-webdriver/bidi/domain.js[53-68]
javascript/selenium-webdriver/bidi/domain.d.ts[18-21]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`queueSubscriptionChange()` keeps a `Map` of `method -> Promise` per BiDi connection in a `WeakMap`, but it never deletes `method` keys. On long-lived connections, repeated subscriptions to many distinct method strings can grow this map without bound and retain Promise chains longer than necessary.

## Issue Context
The queue is keyed by a caller-provided `descriptor.method` (string) from `event()`/`addCallback()`, so it is not inherently bounded unless you add explicit pruning.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/domain.js[55-68]

Suggested direction:
- After `next` settles, delete the `method` entry *if and only if* it still points at the same queued promise you set (i.e., no newer operation has been enqueued). Optionally also gate deletion on `bidi.listenerCount(method) === 0` to avoid churn.
- Example pattern:
 - capture `const queued = next.catch(() => {})`
 - `methods.set(method, queued)`
 - `queued.finally(() => { if (methods.get(method) === queued) methods.delete(method) })`

This keeps serialization correctness while allowing the queue structure to release entries when idle.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. serialization d.ts JSDoc incomplete ✓ Resolved 📘 Rule violation ✧ Quality
Description
The exported defineEnum(), defineRecord(), and defineUnion() declarations in the new
serialization .d.ts files do not include complete, typed @param/@returns JSDoc blocks. This
violates the project's documentation requirements for public API exports.
Code

javascript/selenium-webdriver/bidi/serialization/enum.d.ts[R23-24]

+/** Registers a schema `enum` — a closed set of string values a field may hold. */
+export function defineEnum<T extends string>(name: string, values: readonly T[]): EnumEntry<T>
Evidence
PR Compliance ID 389257 requires complete JSDoc blocks for exported functions/methods, including
typed @param tags for each parameter and typed @returns for non-void returns. The serialization
declaration files add exported factories but only provide brief description comments without the
required typed tags.

Rule 389257: Document public API functions with complete JSDoc blocks
javascript/selenium-webdriver/bidi/serialization/enum.d.ts[23-24]
javascript/selenium-webdriver/bidi/serialization/record.d.ts[59-63]
javascript/selenium-webdriver/bidi/serialization/union.d.ts[27-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Exported public API declarations in the new serialization `.d.ts` files have incomplete JSDoc (missing typed `{...}` `@param` tags and typed `@returns` tags).

## Issue Context
These factory functions (`defineEnum`, `defineRecord`, `defineUnion`) are part of the public API surface and should be documented consistently with the project's JSDoc requirements.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/enum.d.ts[23-24]
- javascript/selenium-webdriver/bidi/serialization/record.d.ts[59-63]
- javascript/selenium-webdriver/bidi/serialization/union.d.ts[27-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. domain.d.ts JSDoc incomplete ✓ Resolved 📘 Rule violation ✧ Quality
Description
The exported event() and Domain.addCallback() declarations have JSDoc blocks that do not provide
typed @param/@returns tags as required for public APIs. This reduces API clarity and can mislead
downstream consumers relying on generated docs.
Code

javascript/selenium-webdriver/bidi/domain.d.ts[R40-42]

+   * @param descriptor An event descriptor from event().
+   * @param handler Invoked with the event's parsed params each time it fires.
+   * @returns A handle for this subscription; call `unsubscribe()` to stop
Evidence
PR Compliance ID 389257 requires complete JSDoc for exported functions/methods, including typed
{...} @param tags for each parameter and a typed @returns for non-void returns. In
domain.d.ts, event() has only a description comment, and Domain.addCallback() uses
@param/@returns without {type} annotations.

Rule 389257: Document public API functions with complete JSDoc blocks
javascript/selenium-webdriver/bidi/domain.d.ts[23-24]
javascript/selenium-webdriver/bidi/domain.d.ts[40-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Public exports in `javascript/selenium-webdriver/bidi/domain.d.ts` have incomplete JSDoc blocks (missing typed `@param` tags and/or typed `@returns`). The compliance rule requires complete JSDoc for exported functions/methods, including `{type}` annotations.

## Issue Context
This file declares the public TypeScript surface for the new BiDi domain layer. Even in `.d.ts` files, exported declarations are part of the public API and should have complete JSDoc blocks.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/domain.d.ts[23-24]
- javascript/selenium-webdriver/bidi/domain.d.ts[34-45]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (8)
14. Nonfinite numbers pass validation ✓ Resolved 🐞 Bug ≡ Correctness
Description
A number node accepts NaN and both infinities because validation only checks `typeof value ===
'number'. When the transport JSON-stringifies these values they become null`, silently sending a
value different from the validated record.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R34-42]

+  if (typeNode.primitive !== undefined) {
+    const expected = { string: 'string', integer: 'number', number: 'number', boolean: 'boolean' }[typeNode.primitive]
+    if (expected && typeof value !== expected) {
+      throw new ValidationError(`${path}: expected ${typeNode.primitive}, got ${typeof value}`)
+    }
+    // `number` admits any JSON number; `integer` rejects a fractional value
+    // (5.7) while still accepting one written 5.0 (Number.isInteger(5.0) is true).
+    if (typeNode.primitive === 'integer' && !Number.isInteger(value)) {
+      throw new ValidationError(`${path}: expected an integer, got ${value}`)
Evidence
The schema maps float/number types to the number primitive, but the validator accepts every
JavaScript value whose typeof is number; the transport subsequently serializes the value with
JSON.stringify.

javascript/selenium-webdriver/project_bidi_schema.mjs[89-107]
javascript/selenium-webdriver/bidi/serialization/record.js[34-44]
javascript/selenium-webdriver/bidi/index.js[217-220]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Primitive number validation accepts nonfinite JavaScript numbers that cannot be represented as BiDi JSON numbers.

## Issue Context
Require `Number.isFinite()` for numeric primitives in addition to the existing integer-specific check, and test both inbound and outbound paths.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[34-44]
- javascript/selenium-webdriver/test/bidi/serialization/record_test.js[127-146]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


15. Wire names serialize incorrectly ✓ Resolved 🐞 Bug ≡ Correctness
Description
The constructor reads each value using field.wire but stores it under field.name, and no
wire-key conversion occurs before the transport calls JSON.stringify(). Whenever name !== wire,
the command sends the JS-facing name instead of the protocol key.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R165-167]

+        const value = data[field.wire]
+        validateValue(field.type, value, `${name}.${field.wire}`, 'outbound')
+        this[field.name] = value
Evidence
The record stores an enumerable field.name property, Domain forwards it unchanged, and the
transport JSON-stringifies that object; the test fixture demonstrates that the API intentionally
permits different name and wire values.

javascript/selenium-webdriver/bidi/serialization/record.js[158-187]
javascript/selenium-webdriver/bidi/domain.js[58-63]
javascript/selenium-webdriver/bidi/index.js[217-227]
javascript/selenium-webdriver/test/bidi/serialization/wire_contract_test.js[100-114]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Outbound records expose JS property names directly to JSON serialization instead of mapping them back to their declared wire names.

## Issue Context
The wire contract test already defines a record where `name` and `wire` differ, but only checks inbound parsing. Add an explicit outbound representation method and cover this fixture through JSON serialization.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[147-188]
- javascript/selenium-webdriver/bidi/domain.js[58-63]
- javascript/selenium-webdriver/test/bidi/serialization/wire_contract_test.js[100-114]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


16. event JSDoc lacks description ✓ Resolved 📘 Rule violation ✧ Quality
Description
The exported event function's JSDoc contains no free-text summary, and its @returns tag has no
description. This leaves the public API documentation incomplete.
Code

javascript/selenium-webdriver/bidi/domain.js[R37-39]

+ * @returns {{method: string, type: ({fromWire(payload: unknown): unknown}|undefined)}}
+ */
+function event(method, type) {
Evidence
The checklist requires a free-text description and a return type with descriptive text. The added
block begins directly with @param, while its @returns tag provides only a type.

Rule 389257: Document public API functions with complete JSDoc blocks
javascript/selenium-webdriver/bidi/domain.js[29-39]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Complete the JSDoc for the exported `event` function with a summary and a descriptive `@returns` tag.

## Issue Context
PR Compliance 389257 requires every exported function to have a complete JSDoc block immediately before its declaration.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/domain.js[29-39]
- javascript/selenium-webdriver/bidi/domain.d.ts[23-23]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


17. defineUnion JSDoc incomplete ✓ Resolved 📘 Rule violation ✧ Quality
Description
The exported defineUnion function's JSDoc lacks a free-text description and documentation for its
non-void return value. The returned union API is therefore not completely documented.
Code

javascript/selenium-webdriver/bidi/serialization/union.js[R42-47]

+/**
+ * @param {string} name Schema type name, e.g. 'session.ProxyConfiguration'.
+ * @param {object} selector The schema's `selector` node for this union.
+ * @param {{objectOnly?: boolean}} [options]
+ */
+function defineUnion(name, selector, options = {}) {
Evidence
The JSDoc contains only parameter tags despite the implementation returning union. It consequently
fails both the description and return documentation requirements.

Rule 389257: Document public API functions with complete JSDoc blocks
javascript/selenium-webdriver/bidi/serialization/union.js[42-47]
javascript/selenium-webdriver/bidi/serialization/union.js[83-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Add a concise summary and a typed, descriptive `@returns` tag to the `defineUnion` JSDoc block.

## Issue Context
The exported factory returns `union`, whose public declaration is `UnionClass<T>`.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/union.js[42-47]
- javascript/selenium-webdriver/bidi/serialization/union.d.ts[27-27]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


18. defineRecord JSDoc incomplete ✓ Resolved 📘 Rule violation ✧ Quality
Description
The exported defineRecord function's JSDoc lacks a free-text description and a typed, described
@returns tag. The generated record class returned by this public factory is undocumented.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R142-147]

+/**
+ * @param {string} name Schema type name, e.g. 'network.AddInterceptParameters'.
+ * @param {Array<{name: string, wire: string, required: boolean, type: object}>} fields
+ * @param {{extensible?: boolean}} [options]
+ */
+function defineRecord(name, fields, options = {}) {
Evidence
The added JSDoc begins with parameter tags and has no return tag, although the implementation
returns Record and the declaration specifies RecordClass<T>.

Rule 389257: Document public API functions with complete JSDoc blocks
javascript/selenium-webdriver/bidi/serialization/record.js[142-147]
javascript/selenium-webdriver/bidi/serialization/record.js[242-245]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Add a concise summary and a typed, descriptive `@returns` tag to `defineRecord`'s JSDoc.

## Issue Context
`defineRecord` is exported and returns the generated `Record` class at the end of the function.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[142-147]
- javascript/selenium-webdriver/bidi/serialization/record.d.ts[56-56]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


19. defineEnum JSDoc incomplete ✓ Resolved 📘 Rule violation ✧ Quality
Description
The exported defineEnum function's JSDoc has no free-text description and omits documentation for
its non-void return value. Consumers are not told that the function returns the registered enum
entry.
Code

javascript/selenium-webdriver/bidi/serialization/enum.js[R20-24]

+/**
+ * @param {string} name Schema type name, e.g. 'network.InterceptPhase'.
+ * @param {string[]} values
+ */
+function defineEnum(name, values) {
Evidence
The block contains only two @param tags, while the implementation returns entry and the
declaration exposes EnumEntry<T>. This violates the required summary and return documentation
criteria.

Rule 389257: Document public API functions with complete JSDoc blocks
javascript/selenium-webdriver/bidi/serialization/enum.js[20-29]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Add a summary and a descriptive typed `@returns` tag to the `defineEnum` JSDoc block.

## Issue Context
The function is exported from the module and returns `entry`, so complete return documentation is required.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/enum.js[20-24]
- javascript/selenium-webdriver/bidi/serialization/enum.d.ts[23-23]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


20. defineAlias lacks tests ✓ Resolved 📘 Rule violation ▣ Testability
Description
The new exported defineAlias behavior is not exercised by any added or existing test under the
JavaScript test tree. A regression in alias registration or alias-based validation could therefore
pass unnoticed.
Code

javascript/selenium-webdriver/bidi/serialization/record.js[R254-256]

+function defineAlias(name, type) {
+  register(name, { kind: 'alias', type })
+}
Evidence
The checklist requires tests that import and exercise every new public function with assertions that
fail if the behavior is reverted. The PR exports defineAlias, while the added serialization tests
exercise records, enums, and unions but never call this function.

Rule 389273: Require tests for all new functionality and bug fixes
javascript/selenium-webdriver/bidi/serialization/record.js[247-258]
javascript/selenium-webdriver/bidi/serialization/record.d.ts[58-58]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Add automated tests that invoke `defineAlias` and assert that records containing references to the alias accept valid values and reject invalid values.

## Issue Context
`defineAlias` is newly exported from both the JavaScript module and its TypeScript declaration, but the test tree contains no invocation of it.

## Fix Focus Areas
- javascript/selenium-webdriver/bidi/serialization/record.js[247-258]
- javascript/selenium-webdriver/test/bidi/serialization/record_test.js[45-191]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


21. Domain callbacks lack JSDoc ✓ Resolved 📘 Rule violation ✧ Quality
Description
The exported Domain class adds public addCallback and removeCallback methods without
immediately preceding JSDoc blocks. Their parameters and non-void promise return values are
therefore undocumented.
Code

javascript/selenium-webdriver/bidi/domain.js[66]

+  async addCallback(descriptor, handler) {
Evidence

[Comment truncated to fit github's 65,536-char limit.]

Comment thread javascript/selenium-webdriver/bidi/domain.js
Comment thread javascript/selenium-webdriver/bidi/domain.js
Comment thread javascript/selenium-webdriver/bidi/serialization/enum.js
Comment thread javascript/selenium-webdriver/bidi/serialization/record.js
Comment thread javascript/selenium-webdriver/bidi/serialization/union.js
Comment thread javascript/selenium-webdriver/bidi/serialization/record.js
Comment thread javascript/selenium-webdriver/bidi/serialization/record.js Outdated
Comment thread javascript/selenium-webdriver/bidi/serialization/record.js
Comment thread javascript/selenium-webdriver/bidi/serialization/record.js Outdated
Comment thread javascript/selenium-webdriver/bidi/serialization/record.js
@AutomatedTester

Copy link
Copy Markdown
Member

just review if we need the jsdoc for this. if you don't think it's needed it's good to merge

Comment thread javascript/selenium-webdriver/bidi/domain.d.ts Outdated
Comment thread javascript/selenium-webdriver/bidi/serialization/enum.d.ts Outdated
Comment thread javascript/selenium-webdriver/bidi/domain.js Outdated
Comment thread javascript/selenium-webdriver/bidi/serialization/record.js Outdated
Comment thread javascript/selenium-webdriver/bidi/serialization/record.js Outdated
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit e8868ce

Comment thread javascript/selenium-webdriver/bidi/domain.js Outdated
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 9b8fb89

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 8bec29d

Comment thread javascript/selenium-webdriver/bidi/domain.js
Comment thread javascript/selenium-webdriver/bidi/domain.js
@qodo-code-review

Copy link
Copy Markdown
Contributor

Code review by qodo was updated up to the latest commit 4670a18

This was referenced Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B-build Includes scripting, bazel and CI integrations B-devtools Includes everything BiDi or Chrome DevTools related C-nodejs JavaScript Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants