using System.Text; using XFESystem.Documents; namespace XFESystem.Browser.Tests; public sealed class NativeScriptAdmissionTests { private const int NativeExternalResourceCharacters = 2_500_000; private static BrowserLimits NativeLimits => new() { MaximumExternalResourceCharacters = NativeExternalResourceCharacters }; [Fact] public void NativeResourceLimitSurvivesCloneAndTrustedProfileWithoutPoisoningThePage() { var trace = new StringBuilder(); var factory = new BrowserService(limits: NativeLimits, resources: new AdmissionResources()) { Trace = value => trace.Append(value) }; var page = Assert.IsType(factory.CreatePageSession()); try { page.ResizeViewport(796, 404); page.NavigateDocument("# Resource admission\n\n" + string.Concat(Enumerable.Repeat("A readable paragraph.\n\n", 80)), "Admission.md"); long prepared = page.PackagedScriptPreparations, reused = page.PackagedScriptCacheHits; // This is the same trusted-document execution profile as the // native preview. Its raised JS limit must not raise fetch limits. page.ExecuteJavaScript(""" globalThis.rejectedDependency=false;globalThis.followingDependency=false; const denied=document.createElement('script');denied.src='/OversizedAdmission.js'; denied.addEventListener('error',()=>{rejectedDependency=true;});document.head.appendChild(denied); const following=document.createElement('script');following.src='/FollowingAdmission.js';document.head.appendChild(following); """); Assert.Equal("true", page.ExecuteJavaScript("rejectedDependency && followingDependency && typeof oversizedDependency==='undefined'")); Assert.Equal(prepared, page.PackagedScriptPreparations); Assert.Equal(reused, page.PackagedScriptCacheHits); Assert.DoesNotContain(page.LoadedResources, resource => resource.Address.EndsWith("/OversizedAdmission.js", StringComparison.Ordinal)); Assert.Contains(page.LoadedResources, resource => resource.Address.EndsWith("/FollowingAdmission.js", StringComparison.Ordinal)); Assert.Contains("XFE_BROWSER_SCRIPT_REJECTED|/OversizedAdmission.js|Reason|ResourceBudget\n", trace.ToString(), StringComparison.Ordinal); Assert.DoesNotContain("XFE_BROWSER_SCRIPT_BEGIN|/OversizedAdmission.js|", trace.ToString(), StringComparison.Ordinal); page.AdvanceTime(1000); page.AdvanceTime(1020); double before = ReadProgress(page); page.ScrollBackgroundDocument(360); page.AdvanceTime(2000); page.AdvanceTime(2020); Assert.Equal(360, page.BackgroundViewport.ScrollY); Assert.True(page.Layout(new(796, 404, 0, 360)).GetBoundingClientRect(page.Document!.QuerySelector(".docs-article")!).Y < -30); Assert.True(ReadProgress(page) > before); Assert.Equal("42", page.ExecuteJavaScript("6*7")); } finally { page.Release(); factory.Release(); } } [Fact] public void OversizedRealMermaidUsesOriginalWebsiteErrorAndSourceFallback() { var trace = new StringBuilder(); var page = new BrowserService(limits: NativeLimits) { Trace = value => trace.Append(value) }; try { page.ResizeViewport(796, 404); page.NavigateDocument("# Mermaid admission\n\n```mermaid\ngraph TD\n A-->B\n```", "Mermaid.md"); for (int time = 0; time <= 200; time += 20) page.AdvanceTime(time); var wrapper = Assert.Single(page.Document!.QuerySelectorAll(".mermaid-wrapper")); Assert.True(wrapper.ClassList.Contains("is-error")); Assert.Equal("图表组件加载失败,已显示原始内容", wrapper.QuerySelector(".mermaid-error-title")!.TextContent); Assert.Contains("graph TD", wrapper.QuerySelector(".mermaid-source code")!.TextContent, StringComparison.Ordinal); Assert.Empty(wrapper.QuerySelectorAll("svg")); Assert.Equal("undefined", page.ExecuteJavaScript("typeof mermaid")); Assert.Contains("XFE_BROWSER_SCRIPT_REJECTED|/js/docs/mermaid.min.js?v=11.16.0|Reason|ResourceBudget\n", trace.ToString(), StringComparison.Ordinal); Assert.DoesNotContain("XFE_BROWSER_SCRIPT_BEGIN|/js/docs/mermaid.min.js", trace.ToString(), StringComparison.Ordinal); Assert.Equal("42", page.ExecuteJavaScript("6*7")); } finally { page.Release(); } } [Fact] public void NativeAdmissionAllowsActualMonacoCoreButRejectsActualLargerMermaidBeforeEvaluation() { var resources = new PackagedBrowserResources(); var address = new Uri(PackagedBrowserResources.MonacoCoreRoot + "EditorCore.js"); var loader = new BrowserResourceLoader(new UnavailableWebTransport(), resources, new(PackagedBrowserResources.MonacoPageAddress), NativeLimits); var core = loader.Get(address, "script"); Assert.Equal(2_293_601, core.Body.Length); Assert.True(core.Body.Length <= NativeExternalResourceCharacters); Assert.True(WebsiteAssets.ReadText("Exact/Mermaid.min.js").Length > NativeExternalResourceCharacters); Assert.Throws(() => loader.Get(new(PackagedBrowserResources.Origin + "/js/docs/mermaid.min.js?v=11.16.0"), "script")); Assert.Single(loader.Receipts); } private static double ReadProgress(BrowserService page) => double.Parse( page.ExecuteJavaScript("parseFloat(document.querySelector('[data-doc-progress]').style.width)"), System.Globalization.CultureInfo.InvariantCulture); private sealed class AdmissionResources : IBrowserResourceSource { private readonly PackagedBrowserResources packaged = new(); private readonly string oversized = "globalThis.oversizedDependency=true;" + new string(' ', NativeExternalResourceCharacters); public bool TryGet(Uri address, out WebResponse response) { if (address.GetLeftPart(UriPartial.Authority) == PackagedBrowserResources.Origin) { if (address.AbsolutePath == "/OversizedAdmission.js") { response = new(address, 200, "application/javascript", oversized); return true; } if (address.AbsolutePath == "/FollowingAdmission.js") { response = new(address, 200, "application/javascript", "followingDependency=true;"); return true; } } return packaged.TryGet(address, out response); } } }