File: src\Aspire.Hosting.Browsers\BrowserLogsCdpProtocol.cs
Web Access
Project: src\tests\Aspire.Hosting.Browsers.Tests\Aspire.Hosting.Browsers.Tests.csproj (Aspire.Hosting.Browsers.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
 
namespace Aspire.Hosting;
 
// Chrome DevTools Protocol (CDP) references:
// - Message envelope and domain index: https://chromedevtools.github.io/devtools-protocol/
// - Target domain: https://chromedevtools.github.io/devtools-protocol/tot/Target/
// - Runtime domain: https://chromedevtools.github.io/devtools-protocol/tot/Runtime/
// - Log domain: https://chromedevtools.github.io/devtools-protocol/tot/Log/
// - Page domain: https://chromedevtools.github.io/devtools-protocol/tot/Page/
// - Network domain: https://chromedevtools.github.io/devtools-protocol/tot/Network/
//
// Browser websocket frames are JSON objects shaped like:
// - command request:  { "id": 1, "method": "...", "params": { ... }, "sessionId": "..."? }
// - command response: { "id": 1, "result": { ... } } or { "id": 1, "error": { ... } }
// - event:            { "method": "...", "params": { ... }, "sessionId": "..."? }
//
// Keep this file focused on protocol serialization and parsing so browser networking and session orchestration can be
// tested independently from CDP frame handling.
internal static class BrowserLogsCdpProtocol
{
    private static readonly JsonWriterOptions s_commandFrameWriterOptions = new()
    {
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
    };
 
    internal const string LogEnableMethod = "Log.enable";
    internal const string LogEntryAddedMethod = "Log.entryAdded";
    internal const string NetworkEnableMethod = "Network.enable";
    internal const string NetworkLoadingFailedMethod = "Network.loadingFailed";
    internal const string NetworkLoadingFinishedMethod = "Network.loadingFinished";
    internal const string NetworkRequestWillBeSentMethod = "Network.requestWillBeSent";
    internal const string NetworkResponseReceivedMethod = "Network.responseReceived";
    internal const string PageEnableMethod = "Page.enable";
    internal const string PageCaptureScreenshotMethod = "Page.captureScreenshot";
    internal const string PageNavigateMethod = "Page.navigate";
    internal const string RuntimeConsoleApiCalledMethod = "Runtime.consoleAPICalled";
    internal const string RuntimeEnableMethod = "Runtime.enable";
    internal const string RuntimeExceptionThrownMethod = "Runtime.exceptionThrown";
    internal const string TargetAttachToTargetMethod = "Target.attachToTarget";
    internal const string TargetCloseTargetMethod = "Target.closeTarget";
    internal const string TargetCreateTargetMethod = "Target.createTarget";
    internal const string TargetDetachedFromTargetMethod = "Target.detachedFromTarget";
    internal const string TargetGetTargetsMethod = "Target.getTargets";
    // Turns on browser-level target discovery. In CDP a "target" is a debuggable entity such as a page/tab, worker,
    // or iframe. We use this subscription for page target lifecycle events; without it, closing or crashing the
    // tracked tab can look like an unexplained connection loss.
    internal const string TargetSetDiscoverTargetsMethod = "Target.setDiscoverTargets";
    internal const string TargetTargetCrashedMethod = "Target.targetCrashed";
    internal const string TargetTargetDestroyedMethod = "Target.targetDestroyed";
    internal const string InspectorDetachedMethod = "Inspector.detached";
 
    internal static BrowserLogsCdpProtocolMessageHeader ParseMessageHeader(ReadOnlySpan<byte> framePayload)
    {
        var reader = new Utf8JsonReader(framePayload, isFinalBlock: true, state: default);
 
        if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
        {
            throw new InvalidOperationException("Tracked browser protocol frame was not a JSON object.");
        }
 
        long? id = null;
        string? method = null;
        string? sessionId = null;
 
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
            {
                break;
            }
 
            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new InvalidOperationException("Tracked browser protocol frame was malformed.");
            }
 
            var propertyName = reader.GetString();
            if (!reader.Read())
            {
                throw new InvalidOperationException("Tracked browser protocol frame ended unexpectedly.");
            }
 
            switch (propertyName)
            {
                case "id":
                    if (!reader.TryGetInt64(out var parsedId))
                    {
                        throw new InvalidOperationException("Tracked browser protocol response id was not an integer.");
                    }
 
                    id = parsedId;
                    break;
                case "method":
                    method = reader.TokenType == JsonTokenType.String
                        ? reader.GetString()
                        : throw new InvalidOperationException("Tracked browser protocol event method was not a string.");
                    break;
                case "sessionId":
                    sessionId = reader.TokenType == JsonTokenType.String
                        ? reader.GetString()
                        : null;
                    break;
                default:
                    reader.Skip();
                    break;
            }
        }
 
        return new BrowserLogsCdpProtocolMessageHeader(id, method, sessionId);
    }
 
    internal static byte[] CreateCommandFrame(long id, string method, string? sessionId, Action<Utf8JsonWriter>? writeParameters)
    {
        var buffer = new ArrayBufferWriter<byte>();
        using var writer = new Utf8JsonWriter(buffer, s_commandFrameWriterOptions);
 
        writer.WriteStartObject();
        writer.WriteNumber("id", id);
        writer.WriteString("method", method);
 
        if (sessionId is not null)
        {
            writer.WriteString("sessionId", sessionId);
        }
 
        if (writeParameters is not null)
        {
            writer.WritePropertyName("params");
            writer.WriteStartObject();
            writeParameters(writer);
            writer.WriteEndObject();
        }
 
        writer.WriteEndObject();
        writer.Flush();
 
        return buffer.WrittenSpan.ToArray();
    }
 
    internal static BrowserLogsCdpProtocolEvent? ParseEvent(BrowserLogsCdpProtocolMessageHeader header, ReadOnlySpan<byte> framePayload) => header.Method switch
    {
        RuntimeConsoleApiCalledMethod => CreateConsoleApiCalledEvent(framePayload),
        RuntimeExceptionThrownMethod => CreateExceptionThrownEvent(framePayload),
        LogEntryAddedMethod => CreateLogEntryAddedEvent(framePayload),
        NetworkRequestWillBeSentMethod => CreateRequestWillBeSentEvent(framePayload),
        NetworkResponseReceivedMethod => CreateResponseReceivedEvent(framePayload),
        NetworkLoadingFinishedMethod => CreateLoadingFinishedEvent(framePayload),
        NetworkLoadingFailedMethod => CreateLoadingFailedEvent(framePayload),
        TargetTargetDestroyedMethod => CreateTargetDestroyedEvent(framePayload),
        TargetTargetCrashedMethod => CreateTargetCrashedEvent(framePayload),
        TargetDetachedFromTargetMethod => CreateDetachedFromTargetEvent(framePayload),
        InspectorDetachedMethod => CreateInspectorDetachedEvent(framePayload),
        _ => null
    };
 
    internal static BrowserLogsCreateTargetResult ParseCreateTargetResponse(ReadOnlySpan<byte> framePayload)
    {
        var envelope = DeserializeFrame(framePayload, BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsCreateTargetResponseEnvelope);
        ThrowIfProtocolError(envelope.Error);
 
        return envelope.Result ?? throw new InvalidOperationException("Tracked browser target creation did not return a result payload.");
    }
 
    internal static BrowserLogsAttachToTargetResult ParseAttachToTargetResponse(ReadOnlySpan<byte> framePayload)
    {
        var envelope = DeserializeFrame(framePayload, BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsAttachToTargetResponseEnvelope);
        ThrowIfProtocolError(envelope.Error);
 
        return envelope.Result ?? throw new InvalidOperationException("Tracked browser target attachment did not return a result payload.");
    }
 
    internal static BrowserLogsGetTargetsResult ParseGetTargetsResponse(ReadOnlySpan<byte> framePayload)
    {
        var envelope = DeserializeFrame(framePayload, BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsGetTargetsResponseEnvelope);
        ThrowIfProtocolError(envelope.Error);
 
        return envelope.Result ?? throw new InvalidOperationException("Tracked browser target discovery did not return a result payload.");
    }
 
    internal static BrowserLogsCommandAck ParseCommandAckResponse(ReadOnlySpan<byte> framePayload)
    {
        var envelope = DeserializeFrame(framePayload, BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsCommandAckResponseEnvelope);
        ThrowIfProtocolError(envelope.Error);
 
        return BrowserLogsCommandAck.Instance;
    }
 
    internal static BrowserLogsCaptureScreenshotResult ParseCaptureScreenshotResponse(ReadOnlySpan<byte> framePayload)
    {
        var envelope = DeserializeFrame(framePayload, BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsCaptureScreenshotResponseEnvelope);
        ThrowIfProtocolError(envelope.Error);
 
        var result = envelope.Result ?? throw new InvalidOperationException("Tracked browser screenshot capture did not return a result payload.");
        if (string.IsNullOrWhiteSpace(result.Data))
        {
            throw new InvalidOperationException("Tracked browser screenshot capture did not return image data.");
        }
 
        return result;
    }
 
    internal static string DescribeFrame(ReadOnlySpan<byte> framePayload, int maxLength = 512)
    {
        var text = Encoding.UTF8.GetString(framePayload);
        return text.Length <= maxLength
            ? text
            : $"{text[..maxLength]}...";
    }
 
    private static BrowserLogsConsoleApiCalledEvent? CreateConsoleApiCalledEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsConsoleApiCalledEnvelope,
            static (string? sessionId, BrowserLogsRuntimeConsoleApiCalledParameters parameters) => new BrowserLogsConsoleApiCalledEvent(sessionId, parameters));
 
    private static BrowserLogsExceptionThrownEvent? CreateExceptionThrownEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsExceptionThrownEnvelope,
            static (string? sessionId, BrowserLogsExceptionThrownParameters parameters) => new BrowserLogsExceptionThrownEvent(sessionId, parameters));
 
    private static BrowserLogsLogEntryAddedEvent? CreateLogEntryAddedEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsLogEntryAddedEnvelope,
            static (string? sessionId, BrowserLogsLogEntryAddedParameters parameters) => new BrowserLogsLogEntryAddedEvent(sessionId, parameters));
 
    private static BrowserLogsRequestWillBeSentEvent? CreateRequestWillBeSentEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsRequestWillBeSentEnvelope,
            static (string? sessionId, BrowserLogsRequestWillBeSentParameters parameters) => new BrowserLogsRequestWillBeSentEvent(sessionId, parameters));
 
    private static BrowserLogsResponseReceivedEvent? CreateResponseReceivedEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsResponseReceivedEnvelope,
            static (string? sessionId, BrowserLogsResponseReceivedParameters parameters) => new BrowserLogsResponseReceivedEvent(sessionId, parameters));
 
    private static BrowserLogsLoadingFinishedEvent? CreateLoadingFinishedEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsLoadingFinishedEnvelope,
            static (string? sessionId, BrowserLogsLoadingFinishedParameters parameters) => new BrowserLogsLoadingFinishedEvent(sessionId, parameters));
 
    private static BrowserLogsLoadingFailedEvent? CreateLoadingFailedEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsLoadingFailedEnvelope,
            static (string? sessionId, BrowserLogsLoadingFailedParameters parameters) => new BrowserLogsLoadingFailedEvent(sessionId, parameters));
 
    private static BrowserLogsTargetDestroyedEvent? CreateTargetDestroyedEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsTargetDestroyedEnvelope,
            static (string? sessionId, BrowserLogsTargetDestroyedParameters parameters) => new BrowserLogsTargetDestroyedEvent(sessionId, parameters));
 
    private static BrowserLogsTargetCrashedEvent? CreateTargetCrashedEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsTargetCrashedEnvelope,
            static (string? sessionId, BrowserLogsTargetCrashedParameters parameters) => new BrowserLogsTargetCrashedEvent(sessionId, parameters));
 
    private static BrowserLogsDetachedFromTargetEvent? CreateDetachedFromTargetEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsDetachedFromTargetEnvelope,
            static (string? sessionId, BrowserLogsDetachedFromTargetParameters parameters) => new BrowserLogsDetachedFromTargetEvent(sessionId, parameters));
 
    private static BrowserLogsInspectorDetachedEvent? CreateInspectorDetachedEvent(ReadOnlySpan<byte> framePayload)
        => CreateEvent(
            framePayload,
            BrowserLogsCdpProtocolJsonContext.Default.BrowserLogsInspectorDetachedEnvelope,
            static (string? sessionId, BrowserLogsInspectorDetachedParameters parameters) => new BrowserLogsInspectorDetachedEvent(sessionId, parameters));
 
    private static TEvent? CreateEvent<TEnvelope, TParameters, TEvent>(
        ReadOnlySpan<byte> framePayload,
        JsonTypeInfo<TEnvelope> jsonTypeInfo,
        Func<string?, TParameters, TEvent> createEvent)
        where TEnvelope : class, IBrowserLogsEventEnvelope<TParameters>
        where TParameters : class
        where TEvent : class
    {
        var envelope = DeserializeFrame(framePayload, jsonTypeInfo);
        return envelope.Params is null
            ? null
            : createEvent(envelope.SessionId, envelope.Params);
    }
 
    private static T DeserializeFrame<T>(ReadOnlySpan<byte> framePayload, JsonTypeInfo<T> jsonTypeInfo)
        where T : class
    {
        return JsonSerializer.Deserialize(framePayload, jsonTypeInfo)
            ?? throw new InvalidOperationException("Tracked browser protocol frame was empty.");
    }
 
    private static void ThrowIfProtocolError(BrowserLogsCdpProtocolError? error)
    {
        if (error is null)
        {
            return;
        }
 
        var message = string.IsNullOrWhiteSpace(error.Message)
            ? "Unknown browser protocol error."
            : error.Message;
 
        if (error.Code is int code)
        {
            throw new InvalidOperationException($"{message} (CDP error {code}).");
        }
 
        throw new InvalidOperationException(message);
    }
}
 
internal readonly record struct BrowserLogsCdpProtocolMessageHeader(long? Id, string? Method, string? SessionId);
 
internal abstract record BrowserLogsCdpProtocolEvent(string Method, string? SessionId);
 
internal sealed record BrowserLogsConsoleApiCalledEvent(string? SessionId, BrowserLogsRuntimeConsoleApiCalledParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.RuntimeConsoleApiCalledMethod, SessionId);
 
internal sealed record BrowserLogsExceptionThrownEvent(string? SessionId, BrowserLogsExceptionThrownParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.RuntimeExceptionThrownMethod, SessionId);
 
internal sealed record BrowserLogsLoadingFailedEvent(string? SessionId, BrowserLogsLoadingFailedParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.NetworkLoadingFailedMethod, SessionId);
 
internal sealed record BrowserLogsLoadingFinishedEvent(string? SessionId, BrowserLogsLoadingFinishedParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.NetworkLoadingFinishedMethod, SessionId);
 
internal sealed record BrowserLogsLogEntryAddedEvent(string? SessionId, BrowserLogsLogEntryAddedParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.LogEntryAddedMethod, SessionId);
 
internal sealed record BrowserLogsRequestWillBeSentEvent(string? SessionId, BrowserLogsRequestWillBeSentParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.NetworkRequestWillBeSentMethod, SessionId);
 
internal sealed record BrowserLogsResponseReceivedEvent(string? SessionId, BrowserLogsResponseReceivedParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.NetworkResponseReceivedMethod, SessionId);
 
// Target lifecycle events differ from page-domain events in routing semantics:
// - For Page/Runtime/Network/Log events, BrowserLogsCdpProtocolEvent.SessionId is the attached page session and
//   the dispatcher routes by matching it against the tracked target's session id.
// - For Target.targetDestroyed/targetCrashed and Inspector.detached, the envelope-level sessionId is typically
//   absent (these are fired on the browser CDP channel, not on a target session). The SUBJECT of the event is
//   carried in the parameters: targetId for target events, the parent attached sessionId for the implicit
//   detach. Routing logic must not rely on BrowserLogsCdpProtocolEvent.SessionId for these.
// - For Target.detachedFromTarget specifically, params.sessionId identifies the session that detached, which is
//   the value that should be matched against the tracked target's session id.
internal sealed record BrowserLogsTargetDestroyedEvent(string? SessionId, BrowserLogsTargetDestroyedParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.TargetTargetDestroyedMethod, SessionId)
{
    public string? TargetId => Parameters.TargetId;
}
 
internal sealed record BrowserLogsTargetCrashedEvent(string? SessionId, BrowserLogsTargetCrashedParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.TargetTargetCrashedMethod, SessionId)
{
    public string? TargetId => Parameters.TargetId;
}
 
internal sealed record BrowserLogsDetachedFromTargetEvent(string? SessionId, BrowserLogsDetachedFromTargetParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.TargetDetachedFromTargetMethod, SessionId)
{
    public string? DetachedSessionId => Parameters.SessionId;
 
    public string? TargetId => Parameters.TargetId;
}
 
internal sealed record BrowserLogsInspectorDetachedEvent(string? SessionId, BrowserLogsInspectorDetachedParameters Parameters)
    : BrowserLogsCdpProtocolEvent(BrowserLogsCdpProtocol.InspectorDetachedMethod, SessionId)
{
    public string? Reason => Parameters.Reason;
}
 
internal sealed class BrowserLogsAttachToTargetResponseEnvelope
{
    [JsonPropertyName("error")]
    public BrowserLogsCdpProtocolError? Error { get; init; }
 
    [JsonPropertyName("id")]
    public long Id { get; init; }
 
    [JsonPropertyName("result")]
    public BrowserLogsAttachToTargetResult? Result { get; init; }
}
 
internal sealed class BrowserLogsAttachToTargetResult
{
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsCommandAck
{
    public static BrowserLogsCommandAck Instance { get; } = new();
 
    private BrowserLogsCommandAck()
    {
    }
}
 
internal sealed class BrowserLogsCommandAckResponseEnvelope
{
    [JsonPropertyName("error")]
    public BrowserLogsCdpProtocolError? Error { get; init; }
 
    [JsonPropertyName("id")]
    public long Id { get; init; }
}
 
internal sealed class BrowserLogsCaptureScreenshotResponseEnvelope
{
    [JsonPropertyName("error")]
    public BrowserLogsCdpProtocolError? Error { get; init; }
 
    [JsonPropertyName("id")]
    public long Id { get; init; }
 
    [JsonPropertyName("result")]
    public BrowserLogsCaptureScreenshotResult? Result { get; init; }
}
 
internal sealed class BrowserLogsCaptureScreenshotResult
{
    [JsonPropertyName("data")]
    public string? Data { get; init; }
}
 
internal interface IBrowserLogsEventEnvelope<out TParameters>
    where TParameters : class
{
    TParameters? Params { get; }
 
    string? SessionId { get; }
}
 
internal sealed class BrowserLogsConsoleApiCalledEnvelope : IBrowserLogsEventEnvelope<BrowserLogsRuntimeConsoleApiCalledParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsRuntimeConsoleApiCalledParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsCreateTargetResponseEnvelope
{
    [JsonPropertyName("error")]
    public BrowserLogsCdpProtocolError? Error { get; init; }
 
    [JsonPropertyName("id")]
    public long Id { get; init; }
 
    [JsonPropertyName("result")]
    public BrowserLogsCreateTargetResult? Result { get; init; }
}
 
internal sealed class BrowserLogsCreateTargetResult
{
    [JsonPropertyName("targetId")]
    public string? TargetId { get; init; }
}
 
internal sealed class BrowserLogsGetTargetsResponseEnvelope
{
    [JsonPropertyName("error")]
    public BrowserLogsCdpProtocolError? Error { get; init; }
 
    [JsonPropertyName("id")]
    public long Id { get; init; }
 
    [JsonPropertyName("result")]
    public BrowserLogsGetTargetsResult? Result { get; init; }
}
 
internal sealed class BrowserLogsGetTargetsResult
{
    [JsonPropertyName("targetInfos")]
    public BrowserLogsTargetInfo[]? TargetInfos { get; init; }
}
 
internal sealed class BrowserLogsTargetInfo
{
    [JsonPropertyName("attached")]
    public bool? Attached { get; init; }
 
    [JsonPropertyName("targetId")]
    public string? TargetId { get; init; }
 
    [JsonPropertyName("type")]
    public string? Type { get; init; }
 
    [JsonPropertyName("url")]
    public string? Url { get; init; }
}
 
internal sealed class BrowserLogsExceptionDetails : BrowserLogsSourceLocation
{
    [JsonPropertyName("exception")]
    public BrowserLogsExceptionObject? Exception { get; init; }
 
    [JsonPropertyName("text")]
    public string? Text { get; init; }
}
 
internal sealed class BrowserLogsExceptionObject
{
    [JsonPropertyName("description")]
    public string? Description { get; init; }
}
 
internal sealed class BrowserLogsExceptionThrownEnvelope : IBrowserLogsEventEnvelope<BrowserLogsExceptionThrownParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsExceptionThrownParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsExceptionThrownParameters
{
    [JsonPropertyName("exceptionDetails")]
    public BrowserLogsExceptionDetails? ExceptionDetails { get; init; }
}
 
internal sealed class BrowserLogsLoadingFailedEnvelope : IBrowserLogsEventEnvelope<BrowserLogsLoadingFailedParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsLoadingFailedParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsLoadingFailedParameters
{
    [JsonPropertyName("blockedReason")]
    public string? BlockedReason { get; init; }
 
    [JsonPropertyName("canceled")]
    public bool? Canceled { get; init; }
 
    [JsonPropertyName("errorText")]
    public string? ErrorText { get; init; }
 
    [JsonPropertyName("requestId")]
    public string? RequestId { get; init; }
 
    [JsonPropertyName("timestamp")]
    public double? Timestamp { get; init; }
}
 
internal sealed class BrowserLogsLoadingFinishedEnvelope : IBrowserLogsEventEnvelope<BrowserLogsLoadingFinishedParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsLoadingFinishedParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsLoadingFinishedParameters
{
    [JsonPropertyName("encodedDataLength")]
    public double? EncodedDataLength { get; init; }
 
    [JsonPropertyName("requestId")]
    public string? RequestId { get; init; }
 
    [JsonPropertyName("timestamp")]
    public double? Timestamp { get; init; }
}
 
internal sealed class BrowserLogsLogEntry : BrowserLogsSourceLocation
{
    [JsonPropertyName("level")]
    public string? Level { get; init; }
 
    [JsonPropertyName("text")]
    public string? Text { get; init; }
}
 
internal sealed class BrowserLogsLogEntryAddedEnvelope : IBrowserLogsEventEnvelope<BrowserLogsLogEntryAddedParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsLogEntryAddedParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsLogEntryAddedParameters
{
    [JsonPropertyName("entry")]
    public BrowserLogsLogEntry? Entry { get; init; }
}
 
internal sealed class BrowserLogsCdpProtocolError
{
    [JsonPropertyName("code")]
    public int? Code { get; init; }
 
    [JsonPropertyName("message")]
    public string? Message { get; init; }
}
 
internal sealed class BrowserLogsCdpProtocolRemoteObject
{
    [JsonPropertyName("description")]
    public string? Description { get; init; }
 
    [JsonPropertyName("unserializableValue")]
    public string? UnserializableValue { get; init; }
 
    [JsonPropertyName("value")]
    public BrowserLogsCdpProtocolValue? Value { get; init; }
}
 
internal sealed class BrowserLogsRequest
{
    [JsonPropertyName("method")]
    public string? Method { get; init; }
 
    [JsonPropertyName("url")]
    public string? Url { get; init; }
}
 
internal sealed class BrowserLogsRequestWillBeSentEnvelope : IBrowserLogsEventEnvelope<BrowserLogsRequestWillBeSentParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsRequestWillBeSentParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsRequestWillBeSentParameters
{
    [JsonPropertyName("redirectResponse")]
    public BrowserLogsResponse? RedirectResponse { get; init; }
 
    [JsonPropertyName("request")]
    public BrowserLogsRequest? Request { get; init; }
 
    [JsonPropertyName("requestId")]
    public string? RequestId { get; init; }
 
    [JsonPropertyName("timestamp")]
    public double? Timestamp { get; init; }
 
    [JsonPropertyName("type")]
    public string? Type { get; init; }
}
 
internal sealed class BrowserLogsResponse
{
    [JsonPropertyName("fromDiskCache")]
    public bool? FromDiskCache { get; init; }
 
    [JsonPropertyName("fromServiceWorker")]
    public bool? FromServiceWorker { get; init; }
 
    [JsonPropertyName("status")]
    public int? Status { get; init; }
 
    [JsonPropertyName("statusText")]
    public string? StatusText { get; init; }
 
    [JsonPropertyName("url")]
    public string? Url { get; init; }
}
 
internal sealed class BrowserLogsResponseReceivedEnvelope : IBrowserLogsEventEnvelope<BrowserLogsResponseReceivedParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsResponseReceivedParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsResponseReceivedParameters
{
    [JsonPropertyName("requestId")]
    public string? RequestId { get; init; }
 
    [JsonPropertyName("response")]
    public BrowserLogsResponse? Response { get; init; }
 
    [JsonPropertyName("type")]
    public string? Type { get; init; }
}
 
internal sealed class BrowserLogsRuntimeConsoleApiCalledParameters
{
    [JsonPropertyName("args")]
    public BrowserLogsCdpProtocolRemoteObject[]? Args { get; init; }
 
    [JsonPropertyName("type")]
    public string? Type { get; init; }
}
 
internal class BrowserLogsSourceLocation
{
    [JsonPropertyName("columnNumber")]
    public int? ColumnNumber { get; init; }
 
    [JsonPropertyName("lineNumber")]
    public int? LineNumber { get; init; }
 
    [JsonPropertyName("url")]
    public string? Url { get; init; }
}
 
internal sealed class BrowserLogsTargetDestroyedEnvelope : IBrowserLogsEventEnvelope<BrowserLogsTargetDestroyedParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsTargetDestroyedParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsTargetDestroyedParameters
{
    [JsonPropertyName("targetId")]
    public string? TargetId { get; init; }
}
 
internal sealed class BrowserLogsTargetCrashedEnvelope : IBrowserLogsEventEnvelope<BrowserLogsTargetCrashedParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsTargetCrashedParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsTargetCrashedParameters
{
    [JsonPropertyName("errorCode")]
    public int? ErrorCode { get; init; }
 
    [JsonPropertyName("status")]
    public string? Status { get; init; }
 
    [JsonPropertyName("targetId")]
    public string? TargetId { get; init; }
}
 
internal sealed class BrowserLogsDetachedFromTargetEnvelope : IBrowserLogsEventEnvelope<BrowserLogsDetachedFromTargetParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsDetachedFromTargetParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsDetachedFromTargetParameters
{
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
 
    [JsonPropertyName("targetId")]
    public string? TargetId { get; init; }
}
 
internal sealed class BrowserLogsInspectorDetachedEnvelope : IBrowserLogsEventEnvelope<BrowserLogsInspectorDetachedParameters>
{
    [JsonPropertyName("params")]
    public BrowserLogsInspectorDetachedParameters? Params { get; init; }
 
    [JsonPropertyName("sessionId")]
    public string? SessionId { get; init; }
}
 
internal sealed class BrowserLogsInspectorDetachedParameters
{
    [JsonPropertyName("reason")]
    public string? Reason { get; init; }
}
 
[JsonConverter(typeof(BrowserLogsCdpProtocolValueJsonConverter))]
internal abstract record BrowserLogsCdpProtocolValue;
 
internal sealed record BrowserLogsCdpProtocolArrayValue(IReadOnlyList<BrowserLogsCdpProtocolValue> Items) : BrowserLogsCdpProtocolValue;
 
internal sealed record BrowserLogsCdpProtocolBooleanValue(bool Value) : BrowserLogsCdpProtocolValue;
 
internal sealed record BrowserLogsCdpProtocolNullValue : BrowserLogsCdpProtocolValue
{
    public static BrowserLogsCdpProtocolNullValue Instance { get; } = new();
 
    private BrowserLogsCdpProtocolNullValue()
    {
    }
}
 
internal sealed record BrowserLogsCdpProtocolNumberValue(string RawValue) : BrowserLogsCdpProtocolValue;
 
internal sealed record BrowserLogsCdpProtocolObjectValue(IReadOnlyDictionary<string, BrowserLogsCdpProtocolValue> Properties) : BrowserLogsCdpProtocolValue;
 
internal sealed record BrowserLogsCdpProtocolStringValue(string Value) : BrowserLogsCdpProtocolValue;
 
internal sealed class BrowserLogsCdpProtocolValueJsonConverter : JsonConverter<BrowserLogsCdpProtocolValue>
{
    public override BrowserLogsCdpProtocolValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => reader.TokenType switch
    {
        JsonTokenType.StartArray => ReadArray(ref reader, options),
        JsonTokenType.StartObject => ReadObject(ref reader, options),
        JsonTokenType.String => new BrowserLogsCdpProtocolStringValue(reader.GetString() ?? string.Empty),
        JsonTokenType.True => new BrowserLogsCdpProtocolBooleanValue(true),
        JsonTokenType.False => new BrowserLogsCdpProtocolBooleanValue(false),
        JsonTokenType.Null => BrowserLogsCdpProtocolNullValue.Instance,
        JsonTokenType.Number => new BrowserLogsCdpProtocolNumberValue(GetRawNumber(ref reader)),
        _ => throw new JsonException($"Unsupported JSON token '{reader.TokenType}' for tracked browser protocol value.")
    };
 
    public override void Write(Utf8JsonWriter writer, BrowserLogsCdpProtocolValue value, JsonSerializerOptions options)
    {
        switch (value)
        {
            case BrowserLogsCdpProtocolArrayValue arrayValue:
                writer.WriteStartArray();
                foreach (var item in arrayValue.Items)
                {
                    Write(writer, item, options);
                }
 
                writer.WriteEndArray();
                break;
            case BrowserLogsCdpProtocolBooleanValue booleanValue:
                writer.WriteBooleanValue(booleanValue.Value);
                break;
            case BrowserLogsCdpProtocolNullValue:
                writer.WriteNullValue();
                break;
            case BrowserLogsCdpProtocolNumberValue numberValue:
                writer.WriteRawValue(numberValue.RawValue, skipInputValidation: true);
                break;
            case BrowserLogsCdpProtocolObjectValue objectValue:
                writer.WriteStartObject();
                foreach (var (propertyName, propertyValue) in objectValue.Properties)
                {
                    writer.WritePropertyName(propertyName);
                    Write(writer, propertyValue, options);
                }
 
                writer.WriteEndObject();
                break;
            case BrowserLogsCdpProtocolStringValue stringValue:
                writer.WriteStringValue(stringValue.Value);
                break;
            default:
                throw new JsonException($"Unsupported tracked browser protocol value type '{value.GetType()}'.");
        }
    }
 
    private static string GetRawNumber(ref Utf8JsonReader reader)
    {
        return reader.HasValueSequence
            ? Encoding.UTF8.GetString(reader.ValueSequence.ToArray())
            : Encoding.UTF8.GetString(reader.ValueSpan);
    }
 
    private static BrowserLogsCdpProtocolArrayValue ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options)
    {
        var items = new List<BrowserLogsCdpProtocolValue>();
 
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndArray)
            {
                break;
            }
 
            items.Add(ReadValue(ref reader, options));
        }
 
        return new BrowserLogsCdpProtocolArrayValue(items);
    }
 
    private static BrowserLogsCdpProtocolObjectValue ReadObject(ref Utf8JsonReader reader, JsonSerializerOptions options)
    {
        var properties = new Dictionary<string, BrowserLogsCdpProtocolValue>(StringComparer.Ordinal);
 
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
            {
                break;
            }
 
            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException("Tracked browser protocol object value was malformed.");
            }
 
            var propertyName = reader.GetString()
                ?? throw new JsonException("Tracked browser protocol object property name was null.");
 
            if (!reader.Read())
            {
                throw new JsonException("Tracked browser protocol object value ended unexpectedly.");
            }
 
            properties[propertyName] = ReadValue(ref reader, options);
        }
 
        return new BrowserLogsCdpProtocolObjectValue(properties);
    }
 
    private static BrowserLogsCdpProtocolValue ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options)
    {
        var converter = (BrowserLogsCdpProtocolValueJsonConverter)options.GetConverter(typeof(BrowserLogsCdpProtocolValue));
        return converter.Read(ref reader, typeof(BrowserLogsCdpProtocolValue), options);
    }
}
 
[JsonSourceGenerationOptions(DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(BrowserLogsAttachToTargetResponseEnvelope))]
[JsonSerializable(typeof(BrowserLogsCaptureScreenshotResponseEnvelope))]
[JsonSerializable(typeof(BrowserLogsCommandAckResponseEnvelope))]
[JsonSerializable(typeof(BrowserLogsConsoleApiCalledEnvelope))]
[JsonSerializable(typeof(BrowserLogsCreateTargetResponseEnvelope))]
[JsonSerializable(typeof(BrowserLogsDetachedFromTargetEnvelope))]
[JsonSerializable(typeof(BrowserLogsGetTargetsResponseEnvelope))]
[JsonSerializable(typeof(BrowserLogsExceptionThrownEnvelope))]
[JsonSerializable(typeof(BrowserLogsInspectorDetachedEnvelope))]
[JsonSerializable(typeof(BrowserLogsLoadingFailedEnvelope))]
[JsonSerializable(typeof(BrowserLogsLoadingFinishedEnvelope))]
[JsonSerializable(typeof(BrowserLogsLogEntryAddedEnvelope))]
[JsonSerializable(typeof(BrowserLogsRequestWillBeSentEnvelope))]
[JsonSerializable(typeof(BrowserLogsResponseReceivedEnvelope))]
[JsonSerializable(typeof(BrowserLogsTargetCrashedEnvelope))]
[JsonSerializable(typeof(BrowserLogsTargetDestroyedEnvelope))]
internal sealed partial class BrowserLogsCdpProtocolJsonContext : JsonSerializerContext;