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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using Jint;
using Jint.Native;
using Jint.Native.Object;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;

namespace XFESystem.Browser;

internal sealed class BrowserPerformance
{
    private sealed record Entry(string Name, string Type, double Start, double Duration, JsValue Detail);
    private readonly Engine engine;
    private readonly BrowserLimits limits;
    private readonly Func<long> clock;
    private readonly List<Entry> entries = [];
    private long droppedEntries;
    public BrowserPerformance(Engine engine, BrowserLimits limits, Func<long> clock)
    {
        this.engine = engine; this.limits = limits; this.clock = clock;
        var target = new BrowserHostObject(engine);
        double origin = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
        Data(target, "timeOrigin", origin);
        var timing = new BrowserHostObject(engine);
        // The legacy timing object must exist for feature detection. Unknown
        // network phases stay zero; they are never invented from wall time.
        foreach (string name in new[] { "navigationStart", "unloadEventStart", "unloadEventEnd", "redirectStart", "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd", "connectStart", "connectEnd", "secureConnectionStart", "requestStart", "responseStart", "responseEnd", "domLoading", "domInteractive", "domContentLoadedEventStart", "domContentLoadedEventEnd", "domComplete", "loadEventStart", "loadEventEnd" })
            Data(timing, name, name == "navigationStart" ? origin : 0);
        Data(target, "timing", timing);
        var navigation = new BrowserHostObject(engine); Data(navigation, "type", 0); Data(navigation, "redirectCount", 0); Data(target, "navigation", navigation);
        target.DefineOwnProperty("droppedEntriesCount", new GetSetPropertyDescriptor(
            new ClrFunction(engine, "get droppedEntriesCount", (_, _) => (double)droppedEntries), null, true, false));
        Function(target, "now", _ => (double)clock());
        Function(target, "mark", args =>
        {
            var options = Value(args, 1); var start = options.IsObject() && options.AsObject().Get("startTime").IsNumber() ? options.AsObject().Get("startTime").AsNumber() : clock();
            if (!double.IsFinite(start) || start < 0) throw new ArgumentException("Invalid performance mark time.");
            return Add(new(Name(Value(args, 0)), "mark", start, 0, options.IsObject() ? options.AsObject().Get("detail") : JsValue.Null));
        });
        Function(target, "measure", args =>
        {
            double start = 0, end = clock(); var options = Value(args, 1); JsValue detail = JsValue.Null;
            if (options.IsObject())
            {
                var settings = options.AsObject(); var from = settings.Get("start"); var to = settings.Get("end"); var duration = settings.Get("duration"); detail = settings.Get("detail");
                if (!from.IsUndefined()) start = Time(from); if (!to.IsUndefined()) end = Time(to);
                if (duration.IsNumber()) { if (!to.IsUndefined() && from.IsUndefined()) start = end - duration.AsNumber(); else end = start + duration.AsNumber(); }
            }
            else { if (!options.IsUndefined()) start = Time(options); if (!Value(args, 2).IsUndefined()) end = Time(Value(args, 2)); }
            if (!double.IsFinite(start + end) || start < 0 || end < start) throw new ArgumentException("Invalid performance measure time.");
            return Add(new(Name(Value(args, 0)), "measure", start, end - start, detail));
        });
        Function(target, "clearMarks", args => { Clear("mark", Value(args, 0)); return JsValue.Undefined; });
        Function(target, "clearMeasures", args => { Clear("measure", Value(args, 0)); return JsValue.Undefined; });
        Function(target, "getEntries", _ => Array(entries));
        Function(target, "getEntriesByName", args => { var name = Name(Value(args, 0)); var type = Value(args, 1); return Array(entries.Where(e => e.Name == name && (type.IsUndefined() || e.Type == Name(type)))); });
        Function(target, "getEntriesByType", args => { var type = Name(Value(args, 0)); return Array(entries.Where(e => e.Type == type)); });
        engine.SetValue("performance", target);
    }
    private JsValue Add(Entry entry)
    {
        if (entries.Count >= limits.MaximumPendingTasks) { entries.RemoveAt(0); ++droppedEntries; }
        entries.Add(entry); return Project(entry);
    }
    private ObjectInstance Project(Entry entry)
    {
        var value = new BrowserHostObject(engine); Data(value, "name", entry.Name); Data(value, "entryType", entry.Type); Data(value, "startTime", entry.Start); Data(value, "duration", entry.Duration); Data(value, "detail", entry.Detail);
        Function(value, "toJSON", _ => value); return value;
    }
    private JsValue Array(IEnumerable<Entry> values) => new JsArray(engine, values.OrderBy(e => e.Start).Select(e => (JsValue)Project(e)).ToArray());
    private double Time(JsValue value)
    {
        if (value.IsNumber()) return value.AsNumber(); var name = Name(value);
        return entries.LastOrDefault(e => e.Type == "mark" && e.Name == name)?.Start ?? throw new ArgumentException("The performance mark does not exist: " + name);
    }
    private void Clear(string type, JsValue name) { if (name.IsUndefined()) entries.RemoveAll(e => e.Type == type); else { var text = Name(name); entries.RemoveAll(e => e.Type == type && e.Name == text); } }
    private static string Name(JsValue value) { if (value.IsObject()) throw new ArgumentException("Performance names must be primitive values."); var name = value.ToString(); if (name.Length > 1024) throw new BrowserLimitException("The performance entry name is too long."); return name; }
    private void Function(ObjectInstance value, string name, Func<JsValue[], JsValue> callback) => value.DefineOwnProperty(name, new PropertyDescriptor(new ClrFunction(engine, name, (_, args) => callback(args)), false, true, false));
    private static void Data(ObjectInstance value, string name, JsValue content) => value.DefineOwnProperty(name, new PropertyDescriptor(content, false, true, false));
    private static JsValue Value(JsValue[] args, int index) => index < args.Length ? args[index] : JsValue.Undefined;
}