File: Services\WebViewRenderer.cs
Web Access
Project: src\src\Components\WebView\WebView\src\Microsoft.AspNetCore.Components.WebView.csproj (Microsoft.AspNetCore.Components.WebView)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Web.Infrastructure;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Components.WebView.Services;
 
internal sealed class WebViewRenderer : WebRenderer
{
    private static readonly RendererInfo _componentPlatform = new("WebView", isInteractive: true);
    private readonly Queue<UnacknowledgedRenderBatch> _unacknowledgedRenderBatches = new();
    private readonly Dispatcher _dispatcher;
    private readonly IpcSender _ipcSender;
    private long nextRenderBatchId = 1;
 
    public WebViewRenderer(
        IServiceProvider serviceProvider,
        Dispatcher dispatcher,
        IpcSender ipcSender,
        ILoggerFactory loggerFactory,
        WebViewJSRuntime jsRuntime,
        JSComponentInterop jsComponentInterop) :
        base(serviceProvider, loggerFactory, jsRuntime.ReadJsonSerializerOptions(), jsComponentInterop)
    {
        _dispatcher = dispatcher;
        _ipcSender = ipcSender;
 
        ElementReferenceContext = jsRuntime.ElementReferenceContext;
    }
 
    public override Dispatcher Dispatcher => _dispatcher;
 
    protected override RendererInfo RendererInfo => _componentPlatform;
 
    protected override int GetWebRendererId() => (int)WebRendererId.WebView;
 
    protected override void HandleException(Exception exception)
    {
        // Notify the JS code so it can show the in-app UI
        _ipcSender.NotifyUnhandledException(exception);
    }
 
    protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
    {
        var batchId = nextRenderBatchId++;
        var tcs = new TaskCompletionSource();
        _unacknowledgedRenderBatches.Enqueue(new UnacknowledgedRenderBatch
        {
            BatchId = batchId,
            CompletionSource = tcs,
        });
 
        _ipcSender.ApplyRenderBatch(batchId, renderBatch);
        return tcs.Task;
    }
 
    protected override void AttachRootComponentToBrowser(int componentId, string domElementSelector)
    {
        _ipcSender.AttachToDocument(componentId, domElementSelector);
    }
 
    public new int AddRootComponent(Type componentType, string domElementSelector)
       => base.AddRootComponent(componentType, domElementSelector);
 
    public new Task RenderRootComponentAsync(int componentId, ParameterView parameters)
       => base.RenderRootComponentAsync(componentId, parameters);
 
    public new void RemoveRootComponent(int componentId)
       => base.RemoveRootComponent(componentId);
 
    public void NotifyRenderCompleted(long batchId)
    {
        var nextUnacknowledgedBatch = _unacknowledgedRenderBatches.Dequeue();
        if (nextUnacknowledgedBatch.BatchId != batchId)
        {
            throw new InvalidOperationException($"Received unexpected acknowledgement for render batch {batchId} (next batch should be {nextUnacknowledgedBatch.BatchId})");
        }
 
        nextUnacknowledgedBatch.CompletionSource.SetResult();
    }
 
    private sealed class UnacknowledgedRenderBatch
    {
        public long BatchId { get; init; }
 
        public TaskCompletionSource CompletionSource { get; init; }
    }
}