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<script>document.title='Forbidden'</script>", "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"));
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<string>(); 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(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++; }
}
}
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<script>document.title='Forbidden'</script>", "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"));
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<string>(); 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(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++; }
}
}