using System; using System.Collections.Generic; using System.IO; using System.Linq; using Paca.Browser.Media; using Xunit; namespace Paca.Tests; public sealed class ModernBrowsingMilestone9Phase109Tests : IDisposable { private readonly string _tempDir; public ModernBrowsingMilestone9Phase109Tests() { _tempDir = Path.Combine(Path.GetTempPath(), "paca_m9_test_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_tempDir); } public void Dispose() { try { if (Directory.Exists(_tempDir)) Directory.Delete(_tempDir, true); } catch { } } [Fact] public void Gate159_VideoFrameCaptureCoordinator_CapturesNativeResolutionFrameMetadataAndPngPipeline() { var outputDir = Path.Combine(_tempDir, "captures"); var coordinator = new VideoFrameCaptureCoordinator(); // Arrange metadata var meta = new VideoFrameMetadata( VideoId: "vid_test_101", Title: "Epic 4K Nature Clip: Ocean Waves", NativeWidth: 3840, NativeHeight: 2160, CurrentTimeSeconds: 72.45, DurationSeconds: 180.0, FrameRate: 60.0, CapturedAtUtc: DateTime.UtcNow ); // Act 1: Suggested file name sanitization var filename = coordinator.GenerateSuggestedFileName(meta.Title, meta.CurrentTimeSeconds); Assert.False(string.IsNullOrWhiteSpace(filename)); Assert.EndsWith(".png", filename, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain(":", filename); // sanitized illegal chars Assert.Contains("Ocean Waves", filename); Assert.Contains("72.45s", filename); // Act 2: Process native resolution capture var dummyRawPixels = new byte[4 * 4 * 4]; // 4x4 RGBA for (int i = 0; i < dummyRawPixels.Length; i += 4) { dummyRawPixels[i] = 0x22; // R dummyRawPixels[i + 1] = 0x88; // G dummyRawPixels[i + 2] = 0xEE; // B dummyRawPixels[i + 3] = 0xFF; // A } var snapshot = coordinator.CreateSnapshot(meta, dummyRawPixels, width: 4, height: 4); Assert.NotNull(snapshot); Assert.Equal(meta, snapshot.Metadata); Assert.True(snapshot.ImageData.Length > 0); // Valid PNG signature check: 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A Assert.Equal(0x89, snapshot.ImageData[0]); Assert.Equal((byte)'P', snapshot.ImageData[1]); Assert.Equal((byte)'N', snapshot.ImageData[2]); Assert.Equal((byte)'G', snapshot.ImageData[3]); // Act 3: Save to directory var savedPath = coordinator.SaveSnapshot(snapshot, outputDir); Assert.True(File.Exists(savedPath)); Assert.Equal(snapshot.ImageData.Length, new FileInfo(savedPath).Length); // Act 4: Aspect ratio & history var ratio = VideoFrameCaptureCoordinator.CalculateAspectRatio(3840, 2160); Assert.Equal("16:9", ratio); var history = coordinator.GetRecentSnapshots(); Assert.Single(history); Assert.Equal(snapshot.SuggestedFileName, history[0].SuggestedFileName); coordinator.ClearHistory(); Assert.Empty(coordinator.GetRecentSnapshots()); } [Fact] public void Gate160_MediaSpeedController_AdjustsSpeedInStepPointsAndPreservesPitch() { var controller = new MediaSpeedController(); // Assert defaults Assert.Equal(1.0, controller.CurrentSpeed); Assert.True(controller.PreservesPitch); // Act 1: Step up controller.StepUp(); Assert.Equal(1.1, controller.CurrentSpeed); // Act 2: Event notification double notifiedSpeed = 0; bool notifiedPitch = false; controller.SpeedChanged += (s, e) => { notifiedSpeed = e.NewSpeed; notifiedPitch = e.PreservesPitch; }; controller.SetSpeed(1.5); Assert.Equal(1.5, controller.CurrentSpeed); Assert.Equal(1.5, notifiedSpeed); Assert.True(notifiedPitch); // Act 3: Boundary clamping (min 0.25, max 4.0) controller.SetSpeed(5.5); Assert.Equal(4.0, controller.CurrentSpeed); controller.SetSpeed(0.1); Assert.Equal(0.25, controller.CurrentSpeed); // Act 4: Pitch preservation toggle controller.SetPreservesPitch(false); Assert.False(controller.PreservesPitch); // Act 5: Generate JS injection script controller.SetSpeed(1.75); var script = controller.GeneratePlaybackScript(); Assert.Contains("playbackRate = 1.75", script); Assert.Contains("preservesPitch = false", script); // Act 6: Reset controller.Reset(); Assert.Equal(1.0, controller.CurrentSpeed); Assert.True(controller.PreservesPitch); } [Fact] public void Gate161_AutoplayPolicyEngine_EnforcesAutoplayPoliciesAndDomainExceptions() { var configPath = Path.Combine(_tempDir, "autoplay_policy.json"); var engine = new AutoplayPolicyEngine(configPath, defaultPolicy: AutoplayPolicy.BlockAudioOnly); // Act 1: Default policy evaluation var decisionSilent = engine.EvaluateAutoplay("https://news.portal.com/article", hasAudioTrack: false); Assert.False(decisionSilent.IsBlocked); Assert.False(decisionSilent.IsMuted); Assert.Equal(AutoplayPolicy.BlockAudioOnly, decisionSilent.AppliedPolicy); var decisionAudio = engine.EvaluateAutoplay("https://news.portal.com/article", hasAudioTrack: true); Assert.False(decisionAudio.IsBlocked); Assert.True(decisionAudio.IsMuted); // Act 2: Domain exception - BlockAll engine.SetDomainPolicy("annoying-ads.com", AutoplayPolicy.BlockAll); var decisionBlockAll = engine.EvaluateAutoplay("https://sub.annoying-ads.com/popup", hasAudioTrack: false); Assert.True(decisionBlockAll.IsBlocked); Assert.Equal(AutoplayPolicy.BlockAll, decisionBlockAll.AppliedPolicy); // Act 3: Domain exception - Allow engine.SetDomainPolicy("youtube.com", AutoplayPolicy.Allow); var decisionAllow = engine.EvaluateAutoplay("https://www.youtube.com/watch?v=123", hasAudioTrack: true); Assert.False(decisionAllow.IsBlocked); Assert.False(decisionAllow.IsMuted); Assert.Equal(AutoplayPolicy.Allow, decisionAllow.AppliedPolicy); // Act 4: Persistence engine.Save(); var reloaded = new AutoplayPolicyEngine(configPath); Assert.Equal(AutoplayPolicy.Allow, reloaded.GetDomainPolicy("youtube.com")); Assert.Equal(AutoplayPolicy.BlockAll, reloaded.GetDomainPolicy("annoying-ads.com")); // Act 5: Remove exception reloaded.RemoveDomainPolicy("youtube.com"); Assert.Equal(reloaded.DefaultPolicy, reloaded.GetDomainPolicy("youtube.com")); } [Fact] public void Gate162_LiveCaptionCoordinator_BuffersSpeechAssemblesSentencesAndEmitsEvents() { var coordinator = new LiveCaptionCoordinator(); Assert.True(coordinator.IsEnabled); var liveUpdates = new List(); var completedSentences = new List(); coordinator.CaptionUpdated += (s, e) => liveUpdates.Add(e.CurrentCaption); coordinator.SentenceCompleted += (s, e) => completedSentences.Add(e.CompletedSentence); // Act 1: Stream speech tokens coordinator.PushChunk("Hello", confidence: 0.95, isDelimiter: false); coordinator.PushChunk("world", confidence: 0.96, isDelimiter: false); Assert.Equal("Hello world", coordinator.ActiveBufferText); Assert.Empty(completedSentences); // Act 2: Delimiter completes sentence coordinator.PushChunk(".", confidence: 0.98, isDelimiter: true); Assert.Single(completedSentences); Assert.Equal("Hello world.", completedSentences[0]); Assert.Equal(string.Empty, coordinator.ActiveBufferText); // Act 3: Assemble sentences from text block coordinator.PushText("Welcome to PACA browser! It is fast and secure."); Assert.Equal(3, completedSentences.Count); Assert.Equal("Welcome to PACA browser!", completedSentences[1]); Assert.Equal("It is fast and secure.", completedSentences[2]); // Act 4: Full transcript and reset var transcript = coordinator.GetFullTranscript(); Assert.Contains("Hello world.", transcript); Assert.Contains("Welcome to PACA browser!", transcript); coordinator.Clear(); Assert.Equal(string.Empty, coordinator.ActiveBufferText); Assert.Equal(string.Empty, coordinator.GetFullTranscript()); } [Fact] public void Gate163_PageVisualFilterEngine_AppliesCssVisualFiltersAndDomainPreferences() { var configPath = Path.Combine(_tempDir, "visual_filters.json"); var engine = new PageVisualFilterEngine(configPath); // Act 1: CSS rule generation for supported filters var grayscaleCss = engine.GetFilterCss(VisualFilterKind.Grayscale, 1.0); Assert.Contains("grayscale(100%)", grayscaleCss); var sepiaCss = engine.GetFilterCss(VisualFilterKind.Sepia, 0.8); Assert.Contains("sepia(80%)", sepiaCss); var invertCss = engine.GetFilterCss(VisualFilterKind.Invert, 1.0); Assert.Contains("invert(100%)", invertCss); var highContrastCss = engine.GetFilterCss(VisualFilterKind.HighContrast, 1.0); Assert.Contains("contrast(", highContrastCss); var readabilityCss = engine.GetFilterCss(VisualFilterKind.ReadabilityHighlight, 1.0); Assert.Contains("contrast(", readabilityCss); Assert.Contains("saturate(", readabilityCss); // Act 2: Script injection generation var script = engine.GenerateFilterInjectionScript(VisualFilterKind.Grayscale, 1.0); Assert.Contains("paca-visual-filter", script); Assert.Contains("grayscale(100%)", script); var clearScript = engine.GenerateClearFilterScript(); Assert.Contains("paca-visual-filter", clearScript); Assert.Contains("remove", clearScript); // Act 3: Per-site domain preferences & persistence engine.SetDomainFilter("reading-site.org", VisualFilterKind.Sepia, 0.85); Assert.Equal(VisualFilterKind.Sepia, engine.GetDomainFilter("reading-site.org").Filter); Assert.Equal(0.85, engine.GetDomainFilter("reading-site.org").Intensity); engine.Save(); var reloaded = new PageVisualFilterEngine(configPath); Assert.Equal(VisualFilterKind.Sepia, reloaded.GetDomainFilter("reading-site.org").Filter); } [Fact] public void Gate164_GpuFallbackPolicy_ManagesSiteAccelerationAndSoftwareRenderingFallbackList() { var configPath = Path.Combine(_tempDir, "gpu_fallback.json"); var policy = new GpuFallbackPolicy(configPath); // Act 1: Default site evaluation var normalSiteDecision = policy.EvaluateSite("https://example.com"); Assert.Equal(GpuRenderingMode.HardwareAccelerated, normalSiteDecision.Mode); Assert.False(normalSiteDecision.IsSoftwareFallback); Assert.True(policy.IsWebGLAllowed("example.com")); // Act 2: Built-in safe software fallback list for known problematic/crash-prone sites Assert.True(policy.IsInSoftwareFallbackList("shadertoy.com")); var fallbackDecision = policy.EvaluateSite("https://www.shadertoy.com/view/123"); Assert.Equal(GpuRenderingMode.SoftwareFallback, fallbackDecision.Mode); Assert.True(fallbackDecision.IsSoftwareFallback); // Act 3: Custom domain override & WebGL disable policy.SetSiteRenderingMode("heavy-app.local", GpuRenderingMode.Disabled); policy.SetWebGLAllowed("heavy-app.local", false); var customDecision = policy.EvaluateSite("https://heavy-app.local/dashboard"); Assert.Equal(GpuRenderingMode.Disabled, customDecision.Mode); Assert.False(policy.IsWebGLAllowed("heavy-app.local")); // Act 4: Chromium command line switch generation var flagsHardware = policy.GetChromiumSwitches("example.com"); Assert.DoesNotContain("--disable-gpu", flagsHardware); var flagsFallback = policy.GetChromiumSwitches("shadertoy.com"); Assert.Contains("--disable-gpu", flagsFallback); // Act 5: Persistence policy.AddToSoftwareFallbackList("crashing-canvas.net", "Known GPU driver freeze"); policy.Save(); var reloaded = new GpuFallbackPolicy(configPath); Assert.True(reloaded.IsInSoftwareFallbackList("crashing-canvas.net")); Assert.Equal(GpuRenderingMode.Disabled, reloaded.GetSiteRenderingMode("heavy-app.local")); } [Fact] public void Gate165_FontOverridePolicy_EnforcesMinimumFontSizeFloorAndFontFamilyOverrides() { var configPath = Path.Combine(_tempDir, "font_override.json"); var policy = new FontOverridePolicy(configPath); // Assert defaults Assert.Equal(14, policy.DefaultOptions.MinimumFontSizePx); Assert.True(policy.DefaultOptions.EnforceMinimumFontSize); // Act 1: Generate CSS rules with minimum font floor & system font var options = new FontPolicyOptions( MinimumFontSizePx: 16, EnforceMinimumFontSize: true, EnableFontOverride: true, PreferredFontFamily: "Segoe UI", MonospaceFontFamily: "Cascadia Code" ); var css = policy.GenerateCss(options); Assert.Contains("min-font-size: 16px", css); Assert.Contains("font-size:", css); Assert.Contains("Segoe UI", css); Assert.Contains("Cascadia Code", css); // Act 2: Generate injection script var script = policy.GenerateInjectionScript(options); Assert.Contains("paca-font-override", script); Assert.Contains("Segoe UI", script); // Act 3: Value clamping (e.g. min font size 9px to 32px) var clampedOptions = policy.SanitizeOptions(new FontPolicyOptions(MinimumFontSizePx: 4)); Assert.Equal(9, clampedOptions.MinimumFontSizePx); var clampedMaxOptions = policy.SanitizeOptions(new FontPolicyOptions(MinimumFontSizePx: 50)); Assert.Equal(32, clampedMaxOptions.MinimumFontSizePx); // Act 4: Per-domain font policy policy.SetDomainPolicy("code.dev", new FontPolicyOptions( MinimumFontSizePx: 18, EnforceMinimumFontSize: true, EnableFontOverride: true, PreferredFontFamily: "Consolas", MonospaceFontFamily: "Consolas" )); var domainPolicy = policy.GetEffectivePolicy("https://sub.code.dev/doc"); Assert.Equal(18, domainPolicy.MinimumFontSizePx); Assert.Equal("Consolas", domainPolicy.PreferredFontFamily); // Act 5: Persistence policy.Save(); var reloaded = new FontOverridePolicy(configPath); var reloadedDomain = reloaded.GetEffectivePolicy("https://code.dev"); Assert.Equal(18, reloadedDomain.MinimumFontSizePx); } [Fact] public void Gate166_DeviceEmulationCoordinator_EmulatesMobileViewportsAndUserAgents() { var coordinator = new DeviceEmulationCoordinator(); // Assert predefined devices (iPhone 16, Galaxy S25, iPad Pro) var devices = coordinator.GetPredefinedDevices(); Assert.Contains(devices, d => d.Name == "iPhone 16"); Assert.Contains(devices, d => d.Name == "Galaxy S25"); Assert.Contains(devices, d => d.Name == "iPad Pro"); // Act 1: Apply iPhone 16 emulation coordinator.ApplyEmulation("iPhone 16"); Assert.NotNull(coordinator.ActiveDevice); Assert.Equal("iPhone 16", coordinator.ActiveDevice.Name); Assert.Equal(393, coordinator.ActiveDevice.ViewportWidth); Assert.Equal(852, coordinator.ActiveDevice.ViewportHeight); Assert.Equal(3.0, coordinator.ActiveDevice.DevicePixelRatio); Assert.True(coordinator.ActiveDevice.HasTouch); Assert.True(coordinator.ActiveDevice.IsMobile); Assert.Contains("iPhone", coordinator.ActiveDevice.UserAgent); // Act 2: Orientation toggle (Portrait <-> Landscape) Assert.Equal(ScreenOrientation.Portrait, coordinator.ActiveDevice.Orientation); coordinator.ToggleOrientation(); Assert.Equal(ScreenOrientation.Landscape, coordinator.ActiveDevice.Orientation); Assert.Equal(852, coordinator.ActiveDevice.ViewportWidth); Assert.Equal(393, coordinator.ActiveDevice.ViewportHeight); // Act 3: Galaxy S25 emulation coordinator.ApplyEmulation("Galaxy S25"); Assert.NotNull(coordinator.ActiveDevice); Assert.Equal(412, coordinator.ActiveDevice.ViewportWidth); Assert.Equal(915, coordinator.ActiveDevice.ViewportHeight); Assert.Contains("Android", coordinator.ActiveDevice.UserAgent); // Act 4: iPad Pro emulation coordinator.ApplyEmulation("iPad Pro"); Assert.NotNull(coordinator.ActiveDevice); Assert.Equal(1024, coordinator.ActiveDevice.ViewportWidth); Assert.Equal(1366, coordinator.ActiveDevice.ViewportHeight); Assert.Contains("iPad", coordinator.ActiveDevice.UserAgent); // Act 5: Generate Emulation JavaScript injection var injectionJs = coordinator.GenerateEmulationScript(coordinator.ActiveDevice); Assert.Contains("navigator.userAgent", injectionJs); Assert.Contains("window.devicePixelRatio", injectionJs); Assert.Contains("ontouchstart", injectionJs); // Act 6: Reset emulation coordinator.ResetEmulation(); Assert.Null(coordinator.ActiveDevice); } }