using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.RenderTree; using Microsoft.Extensions.Logging; namespace AntDesign { public class HtmlRenderer : Renderer { private static readonly HashSet _selfClosingElements = new HashSet(StringComparer.OrdinalIgnoreCase) { "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr" }; private static readonly Task _canceledRenderTask = Task.FromCanceled(new CancellationToken(canceled: true)); private readonly Func _htmlEncoder; public HtmlRenderer(IServiceProvider serviceProvider, ILoggerFactory loggerFactory, Func htmlEncoder) : base(serviceProvider, loggerFactory) { _htmlEncoder = htmlEncoder; } public override Dispatcher Dispatcher { get; } = Dispatcher.CreateDefault(); /// protected override Task UpdateDisplayAsync(in RenderBatch renderBatch) { // By default we return a canceled task. This has the effect of making it so that the // OnAfterRenderAsync callbacks on components don't run by default. // This way, by default prerendering gets the correct behavior and other renderers // override the UpdateDisplayAsync method already, so those components can // either complete a task when the client acknowledges the render, or return a canceled task // when the renderer gets disposed. // We believe that returning a canceled task is the right behavior as we expect that any class // that subclasses this class to provide an implementation for a given rendering scenario respects // the contract that OnAfterRender should only be called when the display has successfully been updated // and the application is interactive. (Element and component references are populated and JavaScript interop // is available). return _canceledRenderTask; } public async Task RenderComponentAsync(IComponent component, ParameterView initialParameters) { var (componentId, frames) = await CreateInitialRenderAsync(component, initialParameters); var context = new HtmlRenderingContext(); var newPosition = RenderFrames(context, frames, 0, frames.Count); Debug.Assert(newPosition == frames.Count); return new ComponentRenderedText(componentId, context.Result); } public async Task RenderComponentAsync(Type componentType, ParameterView initialParameters) { var (componentId, frames) = await CreateInitialRenderAsync(componentType, initialParameters); var context = new HtmlRenderingContext(); var newPosition = RenderFrames(context, frames, 0, frames.Count); Debug.Assert(newPosition == frames.Count); return new ComponentRenderedText(componentId, context.Result); } public Task RenderComponentAsync(ParameterView initialParameters) where TComponent : IComponent { return RenderComponentAsync(typeof(TComponent), initialParameters); } /// protected override void HandleException(Exception exception) => ExceptionDispatchInfo.Capture(exception).Throw(); private int RenderFrames(HtmlRenderingContext context, ArrayRange frames, int position, int maxElements) { var nextPosition = position; var endPosition = position + maxElements; while (position < endPosition) { nextPosition = RenderCore(context, frames, position); if (position == nextPosition) { throw new InvalidOperationException("We didn't consume any input."); } position = nextPosition; } return nextPosition; } private int RenderCore( HtmlRenderingContext context, ArrayRange frames, int position) { ref var frame = ref frames.Array[position]; switch (frame.FrameType) { case RenderTreeFrameType.Element: return RenderElement(context, frames, position); case RenderTreeFrameType.Attribute: throw new InvalidOperationException($"Attributes should only be encountered within {nameof(RenderElement)}"); case RenderTreeFrameType.Text: context.Result.Add(_htmlEncoder(frame.TextContent)); return ++position; case RenderTreeFrameType.Markup: context.Result.Add(frame.MarkupContent); return ++position; case RenderTreeFrameType.Component: return RenderChildComponent(context, frames, position); case RenderTreeFrameType.Region: return RenderFrames(context, frames, position + 1, frame.RegionSubtreeLength - 1); case RenderTreeFrameType.ElementReferenceCapture: case RenderTreeFrameType.ComponentReferenceCapture: return ++position; default: throw new InvalidOperationException($"Invalid element frame type '{frame.FrameType}'."); } } private int RenderChildComponent( HtmlRenderingContext context, ArrayRange frames, int position) { ref var frame = ref frames.Array[position]; var childFrames = GetCurrentRenderTreeFrames(frame.ComponentId); _ = RenderFrames(context, childFrames, 0, childFrames.Count); return position + frame.ComponentSubtreeLength; } private int RenderElement( HtmlRenderingContext context, ArrayRange frames, int position) { ref var frame = ref frames.Array[position]; var result = context.Result; result.Add("<"); result.Add(frame.ElementName); var afterAttributes = RenderAttributes(context, frames, position + 1, frame.ElementSubtreeLength - 1, out var capturedValueAttribute); // When we see an