using AngleSharp.Dom; using XFESystem.Desktop; using System.Text.Json; namespace XFESystem.Browser.Tests; public sealed class DocumentFirstPaintTests { private const string Source = "# Actual source heading\n\nA real **document paragraph**.\n\n```csharp\nConsole.WriteLine(42);\n```"; [Fact] public void FirstDocumentContainsSanitizedSourceAndAllOriginalStylesBeforeAnyScript() { var browser = new BrowserService(); var page = browser.BeginDocumentPreview(Source + "\n\n", "First.md"); Assert.Equal("First.md", page.Title); Assert.Contains("Actual source heading", page.Text, StringComparison.Ordinal); Assert.Contains("document paragraph", page.Text, StringComparison.Ordinal); Assert.DoesNotContain("Forbidden", page.Text, StringComparison.Ordinal); Assert.Equal(9, browser.LoadedResources.Count(resource => resource.Kind == "style" && !resource.Address.ToString().EndsWith("/css/docs/xfesystem-desktop.css", StringComparison.Ordinal))); Assert.Single(browser.LoadedResources, resource => resource.Kind == "style" && resource.Address.ToString().EndsWith("/css/docs/xfesystem-desktop.css", StringComparison.Ordinal)); Assert.DoesNotContain(browser.LoadedResources, resource => resource.Kind == "script"); var list = browser.Layout(new(796, 404)); Assert.Contains(list.Commands, command => command.Kind == BrowserDrawKind.Text && command.Text.Contains("Actual", StringComparison.Ordinal)); Assert.True(browser.DocumentEnhancementsPending); Assert.False(browser.DocumentFirstFramePresented); } [Fact] public void AdvancingOrFinishingRasterWithoutPresentationCannotStartScripts() { var browser = new BrowserService(); browser.BeginDocumentPreview(Source, "First.md"); browser.AdvanceTime(20); browser.Paint(new Canvas(), new(0, 0, 796, 404), 0); browser.AdvanceTime(40); Assert.DoesNotContain(browser.LoadedResources, resource => resource.Kind == "script"); Assert.False(browser.DocumentFirstFramePresented); browser.FramePresented(); Assert.True(browser.DocumentFirstFramePresented); // Runtime construction is its own phase. The first original script // cannot share that phase, and no source or script bytes are removed. browser.AdvanceTime(60); Assert.DoesNotContain(browser.LoadedResources, resource => resource.Kind == "script"); Assert.Equal("{}", browser.ReadWebsiteDocumentProof()); // No expensive/incomplete JS proof during staged loading. long revision = browser.Revision; browser.AdvanceTime(80); Assert.Single(browser.LoadedResources, resource => resource.Kind == "script"); Assert.Equal(revision, browser.Revision); // Pure highlight.js library does not repaint. } [Fact] public void FirstPresentationTraceRequiresRealPaintAndDoesNotIncludeAuthorScriptExecution() { var lines = new List(); var browser = new BrowserService { Trace = lines.Add }; browser.BeginDocumentPreview(Source, "First.md"); browser.FramePresented(); Assert.DoesNotContain(lines, line => line.StartsWith("XFE_DOCUMENT_FIRST_PRESENTED", StringComparison.Ordinal)); browser.Paint(new Canvas(), new(0, 0, 796, 404), 0); browser.FramePresented(); browser.FramePresented(); var trace = Assert.Single(lines, line => line.StartsWith("XFE_DOCUMENT_FIRST_PRESENTED", StringComparison.Ordinal)); Assert.Contains("|OriginalStyles|9|ExecutedScripts|0", trace, StringComparison.Ordinal); using var proof = JsonDocument.Parse(browser.ReadDocumentFirstPaintProof()); Assert.Equal("ActualNativeFirstDocumentPaint", proof.RootElement.GetProperty("Kind").GetString()); Assert.Equal(0, proof.RootElement.GetProperty("ExecutedScriptCount").GetInt32()); Assert.Equal(9, proof.RootElement.GetProperty("OriginalStyleCount").GetInt32()); Assert.Equal(1, proof.RootElement.GetProperty("HostStyleCount").GetInt32()); Assert.Equal(Source.Length, proof.RootElement.GetProperty("SourceCharacters").GetInt32()); Assert.Equal("Actual source heading", proof.RootElement.GetProperty("Heading").GetProperty("Text").GetString()); Assert.True(proof.RootElement.GetProperty("Heading").GetProperty("Runs").GetArrayLength() > 0); long revision = browser.Revision; Assert.Equal(browser.ReadDocumentFirstPaintProof(), browser.ReadDocumentFirstPaintProof()); Assert.Equal(revision, browser.Revision); } [Fact] public void OriginalScriptsRunInOrderAndEventuallyProduceTheSameHighlightedDocument() { var synchronous = new BrowserService(); synchronous.NavigateDocument(Source, "First.md"); for (int i = 1; i <= 4; i++) synchronous.AdvanceTime(i * 20); var staged = new BrowserService(); staged.BeginDocumentPreview(Source, "First.md"); staged.Paint(new Canvas(), new(0, 0, 796, 404), 0); staged.FramePresented(); for (int i = 1; i <= 64; i++) { int scripts = staged.LoadedResources.Count(resource => resource.Kind == "script"); staged.AdvanceTime(i * 20); Assert.InRange(staged.LoadedResources.Count(resource => resource.Kind == "script") - scripts, 0, 1); } Assert.False(staged.DocumentEnhancementsPending); Assert.Equal(synchronous.LoadedResources.Where(resource => resource.Kind == "script").Select(resource => resource.Address), staged.LoadedResources.Where(resource => resource.Kind == "script").Select(resource => resource.Address)); Assert.Equal(synchronous.Document!.QuerySelector(".markdown-body")!.OuterHtml, staged.Document!.QuerySelector(".markdown-body")!.OuterHtml); Assert.DoesNotContain("JavaScript error:", staged.CurrentPage.Text, StringComparison.Ordinal); Assert.Equal("complete", staged.ExecuteJavaScript("document.readyState")); } [Fact] public void NavigatingAwayCancelsAllOldDeferredScriptsAndKeepsNewRuntimeIsolation() { var browser = new BrowserService(); browser.BeginDocumentPreview(Source, "First.md"); browser.Navigate("about:Start"); browser.FramePresented(); browser.AdvanceTime(20); Assert.False(browser.DocumentEnhancementsPending); Assert.False(browser.DocumentFirstFramePresented); Assert.Empty(browser.LoadedResources); Assert.Equal("undefined", browser.ExecuteJavaScript("typeof hljs")); Assert.Equal("XFESystem Browser", browser.CurrentPage.Title); } [Fact] public void ExplicitScriptReadbackBeforePresentationDoesNotReplaceThePendingDocument() { var browser = new BrowserService(); browser.BeginDocumentPreview(Source, "First.md"); Assert.Equal("First.md", browser.ExecuteJavaScript("document.title")); Assert.Contains("Actual source heading", browser.CurrentPage.Text, StringComparison.Ordinal); Assert.True(browser.DocumentEnhancementsPending); Assert.DoesNotContain(browser.LoadedResources, resource => resource.Kind == "script"); } [Fact] public void PixelBindingReadsOnlyThePresentedFrameAndTheStoredProofDoesNotChange() { var browser = new BrowserService(); var canvas = new PresentedCanvas(); browser.BeginDocumentPreview(Source, "Pixels.md"); browser.FramePresented(); Assert.Equal(0, canvas.Reads); browser.Paint(canvas, new(0, 0, 796, 404), 0); Assert.Equal(0, canvas.Reads); Assert.Equal("{}", browser.ReadDocumentFirstPaintProof()); browser.FramePresented(); // A notification cannot replace an actual newer presentation. Assert.Equal(0, canvas.Reads); Assert.False(browser.DocumentFirstFramePresented); canvas.Present(); browser.FramePresented(); Assert.Equal(2, canvas.Reads); string saved = browser.ReadDocumentFirstPaintProof(); using var proof = JsonDocument.Parse(saved); foreach (string name in new[] { "Heading", "Paragraph" }) { var binding = proof.RootElement.GetProperty(name).GetProperty("PresentedPixels"); Assert.Equal("RGB24-FNV1a64", binding.GetProperty("FingerprintAlgorithm").GetString()); Assert.Equal(1, binding.GetProperty("PresentationSequence").GetInt64()); var bounds = binding.GetProperty("NativeBounds"); Assert.True(DesktopPresentedPixels.TryFingerprint(canvas.Presented, 796, 404, 796, new(bounds.GetProperty("X").GetInt32(), bounds.GetProperty("Y").GetInt32(), bounds.GetProperty("Width").GetInt32(), bounds.GetProperty("Height").GetInt32()), 1, out var expected)); Assert.Equal(expected.Fingerprint, binding.GetProperty("Fingerprint").GetString()); } Array.Fill(canvas.Presented, 0xff00); canvas.Present(); browser.FramePresented(); Assert.Equal(saved, browser.ReadDocumentFirstPaintProof()); Assert.Equal(2, canvas.Reads); } private sealed class Canvas : IDesktopCanvas { public int Width => 796; public int Height => 404; public void Fill(uint color, int x, int y, int width, int height) { } public void Text(string text, uint color, int x, int y) { } public void Line(uint color, int x1, int y1, int x2, int y2) { } public void Present() { } } private sealed class PresentedCanvas : IDesktopCanvas, IDesktopPresentedPixelsCanvas { public int Width => 796; public int Height => 404; public int[] Presented { get; } = new int[796 * 404]; public long FramesPresented { get; private set; } public int Reads; public bool TryFingerprintPresentedPixels(DesktopBounds bounds, out DesktopPresentedPixelsFingerprint result) { Assert.True(FramesPresented > 0); Reads++; return DesktopPresentedPixels.TryFingerprint(Presented, Width, Height, Width, bounds, FramesPresented, out result); } public void Fill(uint color, int x, int y, int width, int height) { } public void Text(string text, uint color, int x, int y) { } public void Line(uint color, int x1, int y1, int x2, int y2) { } public void Present() { FramesPresented++; } } }