XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using AngleSharp.Dom;

namespace XFESystem.Browser;

public sealed partial class BrowserService
{
    private readonly HashSet<IElement> executedScripts = new(ReferenceEqualityComparer.Instance);
    private bool drainingResources;
    private bool scriptBudgetExhausted;

    private BrowserDomHost CreateDomHost() => new()
    {
        Mutated = InvalidateDocument,
        FocusChanged = _ => InvalidateDocument(),
        ResourceRequested = element =>
        {
            if (scheduledResources.Add(element)) pendingResources.Enqueue(element);
        },
        Measure = node => EnsureLayout().GetBoundingClientRect(node),
        MeasureRange = (start, startOffset, end, endOffset) => EnsureLayout().GetRangeRects(start,startOffset,end,endOffset),
        HitTest = (x,y) => EnsureLayout().HitTest(x,y)?.Node,
        ComputedStyle = (node, property) => EnsureLayout().GetComputedStyle(node)?.GetPropertyValue(property),
        MeasureText = (text, font) => textMetrics.Measure(text, font),
        RasterizeText = (text, font) => metricCanvas?.RasterizeWebText(text, font.Size, font.Family, font.Weight)
            ?? throw new NotSupportedException("The platform did not supply a real Canvas text rasterizer."),
        Viewport = () => viewport
    };

    private Uri ResourceAddress(string reference)
    {
        Uri basis = currentAddress!;
        string? declared = document?.QuerySelector("base[href]")?.GetAttribute("href");
        if (!string.IsNullOrEmpty(declared) && Uri.TryCreate(basis, declared, out var candidate))
        { ValidateWebAddress(candidate); basis = candidate; }
        var address = new Uri(basis, reference);
        ValidateWebAddress(address); return address;
    }

    private bool RunScript(IElement script)
    {
        CheckOperationCancellation();
        if (scriptBudgetExhausted) return false;
        if (cspDeclared || !executedScripts.Add(script)) return true;
        string type = (script.GetAttribute("type") ?? string.Empty).Trim().ToLowerInvariant();
        if (type == "module") { Diagnostic("Module scripts are not supported by the classic-script interpreter."); return true; }
        if (type is not ("" or "text/javascript" or "application/javascript" or "text/ecmascript" or "application/ecmascript")) return true;
        try
        {
            bool external = script.HasAttribute("src");
            string source = external
                ? resourceLoader!.Get(ResourceAddress(script.GetAttribute("src")!), "script").Body : script.TextContent;
            CheckOperationCancellation();
            Trace?.Invoke("XFE_BROWSER_SCRIPT_BEGIN|" + (script.GetAttribute("src") ?? "Inline") + "|Characters|" + source.Length + "\n");
            CheckOperationCancellation();
            long previousRevision = Revision;
            dom!.CurrentScript = script;
            var result = external && resourceLoader!.LastResponsePackaged
                ? runtime!.EvaluatePackaged(source, ResourceAddress(script.GetAttribute("src")!).AbsoluteUri)
                : runtime!.Evaluate(source);
            dom.CurrentScript = null;
            Trace?.Invoke("XFE_BROWSER_SCRIPT_END|" + (script.GetAttribute("src") ?? "Inline") + "|Success|" + result.Success + "\n");
            CheckOperationCancellation();
            if (result.ConsoleOutput.Length != 0) Diagnostic(result.ConsoleOutput);
            if (!result.Success) Diagnostic("JavaScript error: " + result.Error);
            if (script.HasAttribute("src")) dom!.DispatchEvent(script, result.Success ? "load" : "error", new() { Bubbles = false, Cancelable = false });
            scriptBudgetExhausted |= result.BudgetExceeded;
            // DOM mutation and event callbacks already invalidate their real
            // changes. Loading a pure library need not re-rasterize the page.
            if (!documentEnhancementsPending || Revision != previousRevision) InvalidateDocument();
            return !result.BudgetExceeded;
        }
        catch (OperationCanceledException) when (OperationCancelled) { throw; }
        catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
        {
            dom!.CurrentScript = null;
            Diagnostic("External script failed: " + error.Message);
            dom!.DispatchEvent(script, "error", new() { Bubbles = false, Cancelable = false });
            return true;
        }
    }

    private void LoadStyle(IElement link)
    {
        CheckOperationCancellation();
        if (cspDeclared || !(link.GetAttribute("rel") ?? "").Split(' ', StringSplitOptions.RemoveEmptyEntries).Contains("stylesheet", StringComparer.OrdinalIgnoreCase)) return;
        try
        {
            if (linkedStyles.Count(item => !ReferenceEquals(item.Owner, link)) >= activeLimits.MaximumStyleSheets) throw new BrowserLimitException("The stylesheet count limit was reached.");
            var response = resourceLoader!.Get(ResourceAddress(link.GetAttribute("href") ?? ""), "style");
            CheckOperationCancellation();
            Trace?.Invoke("XFE_BROWSER_STYLE|" + response.Address + "|Characters|" + response.Body.Length + "\n");
            linkedStyles.RemoveAll(item => ReferenceEquals(item.Owner, link));
            linkedStyles.Add((response.Body, link)); InvalidateDocument();
            dom?.DispatchEvent(link, "load", new() { Bubbles = false, Cancelable = false });
        }
        catch (OperationCanceledException) when (OperationCancelled) { throw; }
        catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
        { Diagnostic("Stylesheet failed: " + error.Message); dom?.DispatchEvent(link, "error", new() { Bubbles = false, Cancelable = false }); }
    }

    private void DrainResources(int maximum = int.MaxValue)
    {
        if (drainingResources || dom is null) return;
        if (documentEnhancementsPending) maximum = Math.Min(maximum, 1);
        drainingResources = true;
        // Explicit success/catch cleanup is needed by the current native runtime.
        try
        {
            int count = 0;
            while (count < maximum && pendingResources.TryDequeue(out var element))
            {
                CheckOperationCancellation();
                scheduledResources.Remove(element);
                if (++count > activeLimits.MaximumExternalResources) throw new BrowserLimitException("The dynamic resource queue limit was reached.");
                if (element.Owner != document || !IsConnected(element)) continue;
                if (element.LocalName == "script") RunScript(element); else if (element.LocalName == "link") LoadStyle(element);
            }
        }
        catch { drainingResources = false; throw; }
        drainingResources = false;
    }

    private static bool IsConnected(INode node)
    { while (node.Parent is not null) node = node.Parent; return node.NodeType == NodeType.Document; }
    private void Diagnostic(string message) => pageDiagnostics?.Append(BoundedText.Safe(message, 2_048) + "\n");
}