Repository: dotnet/aspnetcore
File: src/Components/WebAssembly/Server/src/TargetPickerUi.cs (C#)

Code before:

                        .inspectable-page h3 {
                            margin-top: 0px;
                            margin-bottom: 0.3rem;
                            color: black;
                        }
                    </style>
                ");

            foreach (var tab in matchingTabs)
            {
                var devToolsUrlWithProxy = GetDevToolsUrlWithProxy(tab);
                await context.Response.WriteAsync(
                    $"<a class='inspectable-page' href='{WebUtility.HtmlEncode(devToolsUrlWithProxy)}'>"
                    + $"<h3>{WebUtility.HtmlEncode(tab.Title)}</h3>{WebUtility.HtmlEncode(tab.Url)}"
                    + $"</a>");
            }
        }
    }

    private string GetDevToolsUrlWithProxy(BrowserTab tabToDebug)
    {
        var underlyingV8Endpoint = new Uri(tabToDebug.WebSocketDebuggerUrl);
        var proxyEndpoint = new Uri(_debugProxyUrl);
        var devToolsUrlAbsolute = new Uri(new Uri(_browserHost), relativeUri: NormalizeDevtoolsFrontendUrl(tabToDebug.DevtoolsFrontendUrl));
        var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{underlyingV8Endpoint.Scheme}={proxyEndpoint.Authority}{underlyingV8Endpoint.PathAndQuery}";
        return devToolsUrlWithProxy;

        static string NormalizeDevtoolsFrontendUrl(string devtoolsFrontendUrl)
        {
            // Frontend url can be absolute or relative based on browser
            string localPath;
            string queryString;
            if (Uri.TryCreate(devtoolsFrontendUrl, UriKind.Absolute, out var devtoolsFrontendUri))
            {
                localPath = devtoolsFrontendUri.LocalPath;
                queryString = devtoolsFrontendUri.Query;
            }
            else
            {
                var queryStringBeginCharIndex = devtoolsFrontendUrl.IndexOf('?');
                localPath = devtoolsFrontendUrl[..queryStringBeginCharIndex];
                queryString = devtoolsFrontendUrl[queryStringBeginCharIndex..];
            }

            // We need only the last segment of the path
            localPath = localPath.Split('/').Last().TrimStart('/');
            // Ensure starts with "devtools"
            localPath = $"devtools/{localPath}";

            return $"{localPath}?{queryString}";

Review comment (bug):
Is the scheme and host part of the `DevToolsFrontendUrl` different from those of `_browserHost`?

Could the logic here be something like `Uri.TryCreate(tabToDebug.DevtoolsFrontendUrl, UriKind.Absolute, out var devToolsAbsolute) ? devToolsAbsolute : new Uri(_browserHost + tabToDebug.DevtoolsFrontendUrl)` instead?

Code after:
                        }

                        .inspectable-page h3 {
                            margin-top: 0px;
                            margin-bottom: 0.3rem;
                            color: black;
                        }
                    </style>
                ");

            foreach (var tab in matchingTabs)
            {
                var devToolsUrlWithProxy = GetDevToolsUrlWithProxy(tab);
                await context.Response.WriteAsync(
                    $"<a class='inspectable-page' href='{WebUtility.HtmlEncode(devToolsUrlWithProxy)}'>"
                    + $"<h3>{WebUtility.HtmlEncode(tab.Title)}</h3>{WebUtility.HtmlEncode(tab.Url)}"
                    + $"</a>");
            }
        }
    }

    private string GetDevToolsUrlWithProxy(BrowserTab tabToDebug)
    {
        var underlyingV8Endpoint = new Uri(tabToDebug.WebSocketDebuggerUrl);
        var proxyEndpoint = new Uri(_debugProxyUrl);
        var devToolsUrlAbsolute = new Uri(new Uri(_browserHost), relativeUri: NormalizeDevtoolsFrontendUrl(tabToDebug.DevtoolsFrontendUrl));
        var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{underlyingV8Endpoint.Scheme}={proxyEndpoint.Authority}{underlyingV8Endpoint.PathAndQuery}";
        return devToolsUrlWithProxy;

        static string NormalizeDevtoolsFrontendUrl(string devtoolsFrontendUrl)
        {
            // Currently frontend url can be:
            // - absolute (since v135 of chrome and edge)
            //      chrome example: https://chrome-devtools-frontend.appspot.com/serve_rev/@031848bc6ad02b97854f3d6154d3aefd0434756a/inspector.html?ws=localhost:9222/devtools/page/719FE9D3B43570193235446E0AB36859
            //      edge example: https://aka.ms/docs-landing-page/serve_rev/@4e2c41645f24197463afa2ab6aa999352ee8255c/inspector.html?ws=localhost:9222/devtools/page/3A4D56E09776321628432588FC9299F4
            // - relative (managed as fallback for brosers with prior version)
            //      example: /devtools/inspector.html?ws=localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734
            // The absolute url can't be used as-is because is not valid for debugging and cannot be made relative because of lack "devtools" segment
            // before "inspector.html" but we can keep the query string and append to the default "devtools/inspector.html" browser devtools page

            const string DefaultBrowserDevToolsPagePath = "devtools/inspector.html";

            if (devtoolsFrontendUrl.AsSpan().TrimStart('/').StartsWith(DefaultBrowserDevToolsPagePath))
            {
                return devtoolsFrontendUrl;
            }

            UriHelper.FromAbsolute(devtoolsFrontendUrl, out _, out _, out _, out var query, out _);
            return $"{DefaultBrowserDevToolsPagePath}{query}";
        }
    }