Skip to content

Expose V8InspectorSession remote-object lifecycle APIs #2033

Description

@satouriko

Summary

V8 exposes the following public methods on
V8InspectorSession:

  • wrapObject
  • unwrapObject
  • releaseObjectGroup

These methods are not currently exposed by rusty_v8.

Without them, a Rust embedder cannot use the Inspector's own remote-object
identity system from an embedder-provided Inspector command:

  • A Local<Value> cannot be converted into a Runtime.RemoteObject with an
    Inspector-generated objectId.
  • An objectId received from the frontend cannot be resolved back to its V8
    value and context.
  • An embedder cannot directly release an object group that it created.

Impact

The built-in Runtime and Debugger domains continue to work because V8 uses
these mechanisms internally.

The missing bindings affect embedder-provided Inspector functionality that
needs to exchange object references with the frontend. Without them, an
embedder would have to invent a separate object-ID registry instead of using
the Inspector's injected-script and object-group state.

That produces object IDs which are not naturally interoperable with standard
CDP operations such as Runtime.getProperties, and duplicates lifecycle
management already implemented by V8.

Proposed API

Expose an opaque owned representation of the Inspector RemoteObject:

pub struct RemoteObject {
    // Owns a protocol::Runtime::API::RemoteObject.
}

impl RemoteObject {
    /// Serialize using the Inspector's CRDTP/CBOR representation.
    pub fn to_bytes(&self) -> Vec<u8>;
}

Add the corresponding session operations:

impl V8InspectorSession {
    pub fn wrap_object<'s>(
        &self,
        scope: &mut PinScope<'s, '_>,
        context: Local<'s, Context>,
        value: Local<'s, Value>,
        object_group: StringView,
        generate_preview: bool,
    ) -> Option<RemoteObject>;

    pub fn unwrap_object<'s>(
        &self,
        scope: &mut PinScope<'s, '_>,
        object_id: StringView,
    ) -> Result<UnwrappedObject<'s>, String>;

    pub fn release_object_group(
        &self,
        object_group: StringView,
    );
}

The unwrap result would contain all values returned by V8:

pub struct UnwrappedObject<'s> {
    pub value: Local<'s, Value>,
    pub context: Local<'s, Context>,
    pub object_group: Option<String>,
}

wrap_object returns None when V8 cannot find an injected script for the
context or otherwise cannot create the RemoteObject.

unwrap_object returns V8's error message for an invalid or stale object ID.

The returned Local handles are tied to the caller's scope.

RemoteObject representation

The proposal uses an opaque owned RemoteObject rather than returning a JSON
string directly.

Its to_bytes method would use the generated RemoteObject's
AppendSerialized implementation, producing the same CBOR representation used
by the existing CRDTP APIs. Callers that need JSON can use the existing
crdtp::cbor_to_json conversion.

This avoids:

  • forcing every caller through JSON;
  • exposing generated protocol classes directly;
  • making raw CRDTP pointer types public;
  • treating protocol data as an unvalidated UTF-8 string.

If maintainers prefer a smaller API surface, an alternative is for
wrap_object to return Option<Vec<u8>> containing the serialized CBOR
directly.

Ownership and safety

  • The C++ shim receives the std::unique_ptr<RemoteObject> from V8 and
    transfers it to an owning Rust wrapper.
  • Destruction of the protocol object remains on the C++ side.
  • No generated C++ object layout is exposed to Rust.
  • unwrap_object copies the small error and object-group strings into owned
    Rust strings.
  • Returned V8 handles remain bounded by the supplied scope.
  • Only public V8 Inspector APIs are used.

Tests

The binding should include tests verifying that:

  1. Wrapping an object returns a serializable Runtime.RemoteObject.
  2. The result contains a non-empty Inspector-generated objectId.
  3. Unwrapping that ID returns the original value and context.
  4. The original object group is returned.
  5. An invalid object ID produces an error.
  6. Releasing the object group makes its previous IDs invalid.
  7. Wrapping fails cleanly when the context has no usable injected script.
  8. Repeated wrapping and releasing does not leak protocol objects or V8
    handles.

Open questions

  1. Should wrap_object return an opaque owned RemoteObject, as proposed, or
    serialized CBOR bytes?
  2. Should unwrap errors and object-group names be copied into Rust String
    values, or should the API preserve V8's owned StringBuffer types?
  3. Is one PR for all three lifecycle operations preferred, or should the
    implementation be delivered as:
    • wrap_object and RemoteObject serialization first;
    • unwrap_object and release_object_group second?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions