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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
namespace XFESystem.Browser.Tests;

public sealed class BrowserEventLoopTests
{
    [Fact]
    public void TimersWaitForExplicitMonotonicPumpAndCancellationWorks()
    {
        var runtime = new JavaScriptRuntime();
        Assert.True(runtime.Evaluate("var values=[];setTimeout((a,b)=>values.push(a+b),10,20,22);const gone=setTimeout(()=>values.push(99),0);clearTimeout(gone);").Success);
        Assert.True(runtime.Pump(9).Success); Assert.Equal("", runtime.Evaluate("values.join(',')").Value);
        Assert.True(runtime.Pump(10).Success); Assert.Equal("42", runtime.Evaluate("values.join(',')").Value); Assert.Equal(0, runtime.PendingTaskCount);
    }

    [Fact]
    public void IntervalsDoNotCatchUpInUnboundedLoopsAndCanCancelThemselves()
    {
        var runtime = new JavaScriptRuntime();
        runtime.Evaluate("var ticks=0;var timer=setInterval(()=>{if(++ticks===2)clearInterval(timer);},1)");
        runtime.Pump(1_000); Assert.Equal("1", runtime.Evaluate("ticks").Value);
        runtime.Pump(1_000); Assert.Equal("1", runtime.Evaluate("ticks").Value);
        runtime.Pump(1_001); Assert.Equal("2", runtime.Evaluate("ticks").Value); Assert.Equal(0, runtime.PendingTaskCount);
    }

    [Fact]
    public void RafRecursionWaitsForNextFrameAndReceivesMonotonicTime()
    {
        var runtime = new JavaScriptRuntime(); runtime.Evaluate("var frames=[];function frame(t){frames.push(t);requestAnimationFrame(frame);}requestAnimationFrame(frame)");
        runtime.Pump(15); Assert.Equal("15", runtime.Evaluate("frames.join(',')").Value);
        runtime.Pump(14); Assert.Equal("15", runtime.Evaluate("frames.join(',')").Value);
        runtime.Pump(16); Assert.Equal("15,16", runtime.Evaluate("frames.join(',')").Value);
    }

    [Fact]
    public void RealPromiseAndQueueMicrotaskOrderPrecedesTimers()
    {
        var runtime = new JavaScriptRuntime(); var result = runtime.Evaluate("var order=[];setTimeout(()=>order.push('timer'),0);Promise.resolve().then(()=>order.push('promise'));queueMicrotask(()=>order.push('micro'));order.push('sync');");
        Assert.True(result.Success, result.Error); Assert.Equal("sync,promise,micro", runtime.Evaluate("order.join(',')").Value);
        runtime.Pump(0); Assert.Equal("sync,promise,micro,timer", runtime.Evaluate("order.join(',')").Value);
    }

    [Fact]
    public void PromisePolyfillUsingQueueMicrotaskCannotRecursivelyReplaceTheHostsQueue()
    {
        using var runtime = new JavaScriptRuntime();
        var result = runtime.Evaluate("""
            var order=[],NativePromise=Promise;
            globalThis.Promise={resolve(value){return {then(callback){queueMicrotask(()=>callback(value))}}}};
            Promise.resolve('polyfill').then(value=>order.push(value));
            queueMicrotask(()=>order.push('micro'));NativePromise.resolve().then(()=>order.push('native'));
            order.push('sync');
            """);
        Assert.True(result.Success, result.Error);
        Assert.Equal("sync,polyfill,micro,native", runtime.Evaluate("order.join(',')").Value);
        Assert.Equal(0, runtime.PendingTaskCount);
    }

    [Fact]
    public void HiddenMicrotaskAnchorKeepsItsOriginalThenAndSpeciesWhenPageReplacesThePrototype()
    {
        using var runtime = new JavaScriptRuntime();
        var result = runtime.Evaluate("""
            var ran=0;Promise.prototype.then=function(){throw Error('page then')};
            Promise.prototype.constructor=function(){throw Error('page constructor')};
            globalThis.Promise=function(){throw Error('page Promise')};queueMicrotask(()=>ran++);
            """);
        Assert.True(result.Success, result.Error); Assert.Equal("1", runtime.Evaluate("ran").Value);
    }

    [Fact]
    public void CallbackCountQueueSizeAndCallbackExecutionAreBounded()
    {
        var runtime = new JavaScriptRuntime(new() { MaximumPendingTasks = 4, MaximumEventCallbacksPerPump = 2, MaximumStatements = 100 });
        Assert.True(runtime.Evaluate("var count=0;for(let i=0;i<4;i++)setTimeout(()=>++count,0)").Success);
        Assert.False(runtime.Evaluate("setTimeout(()=>{},0)").Success);
        runtime.Pump(0); Assert.Equal("2", runtime.Evaluate("count").Value); Assert.Equal(2, runtime.PendingTaskCount);
        runtime.Pump(0); Assert.Equal("4", runtime.Evaluate("count").Value);
        runtime.Evaluate("setTimeout(()=>{while(true){}},0)"); Assert.False(runtime.Pump(0).Success);
        Assert.Equal("42", runtime.Evaluate("6*7").Value);
    }

    [Fact]
    public void StringTimerSourcesAreRejectedAndClockCannotRegress()
    {
        var runtime = new JavaScriptRuntime(); Assert.False(runtime.Evaluate("setTimeout('alert(1)',0)").Success);
        runtime.Pump(100); runtime.Pump(2); Assert.Equal("100", runtime.Evaluate("performance.now()").Value);
    }

    [Fact]
    public void BoundAndProxyFunctionsAreLegitimateTimerCallbacks()
    {
        var runtime = new JavaScriptRuntime();
        var result = runtime.Evaluate("var values=[];setTimeout(function(x){values.push(this.base+x)}.bind({base:40},2),0);setTimeout(new Proxy(()=>values.push(43),{}),0)");
        Assert.True(result.Success, result.Error); Assert.True(runtime.Pump(0).Success);
        Assert.Equal("42,43", runtime.Evaluate("values.join(',')").Value);
    }

    [Fact]
    public void PerformanceMarksAndMeasuresReflectActualPumpedTimeAndCanBeCleared()
    {
        var runtime = new JavaScriptRuntime(); Assert.True(runtime.Evaluate("performance.mark('start')").Success); runtime.Pump(25);
        var result = runtime.Evaluate("performance.mark('end');performance.measure('elapsed','start','end');performance.getEntriesByType('measure')[0].duration");
        Assert.True(result.Success, result.Error); Assert.Equal("25", result.Value);
        Assert.Equal("2", runtime.Evaluate("performance.getEntriesByType('mark').length").Value);
        runtime.Evaluate("performance.clearMarks('start');performance.clearMeasures();");
        Assert.Equal("1", runtime.Evaluate("performance.getEntries().length").Value);
        Assert.False(runtime.Evaluate("performance.measure('bad','unknown')").Success);
    }

    [Fact]
    public void PerformanceBufferRetainsBoundedRecentEntriesAndReportsEviction()
    {
        var runtime = new JavaScriptRuntime(new() { MaximumPendingTasks = 4 });
        var result = runtime.Evaluate("for(let i=0;i<10;i++)performance.mark('mark'+i);[performance.getEntries().length,performance.droppedEntriesCount,performance.getEntries()[0].name].join(':')");
        Assert.True(result.Success, result.Error); Assert.Equal("4:6:mark6", result.Value);
    }
}