using System.Diagnostics; using System.Security.Cryptography; using System.Text.Json; using AngleSharp.Dom; using AngleSharp.Html.Parser; using Jint.Runtime.Interop; using XFESystem.Browser; var root = Path.GetFullPath(args.Length > 0 ? args[0] : "."); var snapshot = Path.GetFullPath(args.Length > 1 ? args[1] : Path.Combine(root,"build/BrowserProbe/CurrentDom")); var assets = Path.GetFullPath(Path.Combine(root, "assets/Browser/Monaco/0.52.2")); var output = Path.Combine(root, "build/BrowserProbe", "CurrentDom-" + DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()); Directory.CreateDirectory(output); var document = new HtmlParser().ParseDocument("
"); var limits = new BrowserLimits { MaximumScriptCharacters = 8_388_608, MaximumScriptMemoryBytes = 536_870_912, MaximumStatements = 100_000_000, MaximumRecursionDepth = 256, ScriptTimeout = TimeSpan.FromSeconds(60), MaximumOutputCharacters = 1_048_576, MaximumDomNodes = 100_000 }; var runtime = new JavaScriptRuntime(limits); runtime.Engine.SetValue("__xfeTimerType", new ClrFunction(runtime.Engine,"timerType",(_,values)=>values[0].GetType().FullName ?? "unknown")); var resources = new Queue(); var resourceLog = new List(); var stages = new List(); var sheets = new List(); int mutations = 0; var bridge = new DomBridge(runtime, document, new Uri("http://xfe.test/Monaco/Index.html"), limits, new() { ResourceRequested = node => resources.Enqueue(node), Mutated = () => mutations++, Viewport = () => new(800,600) }); bool Step(string name, string source) { var stopwatch = Stopwatch.StartNew(); var result = runtime.Evaluate(source); stages.Add(new { Name=name, result.Success, result.Error, result.Value, result.ConsoleOutput, Milliseconds=stopwatch.Elapsed.TotalMilliseconds }); Console.WriteLine(JsonSerializer.Serialize(new { Name=name, result.Success, result.Error, result.Value, result.ConsoleOutput })); return result.Success; } void PumpResources() { for (int count=0; resources.Count>0 && count<128; count++) { var element=resources.Dequeue(); var relative=element.GetAttribute(element.LocalName=="link"?"href":"src") ?? ""; var address=new Uri(new Uri("http://xfe.test/Monaco/Index.html"),relative); if(address.GetLeftPart(UriPartial.Authority)!="http://xfe.test" || !address.AbsolutePath.StartsWith("/Monaco/min/",StringComparison.Ordinal)) throw new InvalidOperationException("Only pinned offline Monaco resource URLs are allowed: "+address); var file=Path.GetFullPath(Path.Combine(assets,address.AbsolutePath["/Monaco/".Length..].Replace('/',Path.DirectorySeparatorChar))); if(!file.StartsWith(assets+Path.DirectorySeparatorChar,StringComparison.OrdinalIgnoreCase))throw new InvalidOperationException("Asset traversal rejected"); var source=File.ReadAllText(file); bool success=true;string error=""; if(element.LocalName=="script") { try { bridge.CurrentScript=element;if(source.Length>limits.MaximumScriptCharacters)throw new InvalidOperationException("Resource script budget exceeded");runtime.Engine.Execute(source); } catch(Exception exception) {success=false;error=exception.ToString();Console.WriteLine("TIMER_CALLS "+runtime.Engine.Evaluate("JSON.stringify(globalThis.__xfeTimerCalls)").ToString());} finally {bridge.CurrentScript=null;} } else sheets.Add(new(source,element,address.AbsoluteUri)); resourceLog.Add(new { Address=address.AbsoluteUri, Kind=element.LocalName, Characters=source.Length, Sha256=Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(file))), Success=success, Error=error }); Console.WriteLine(JsonSerializer.Serialize(resourceLog[^1])); bridge.DispatchEvent(element,success?"load":"error",new(){Bubbles=false,Cancelable=false}); if(!success)break; } } try { Step("RealLoader",File.ReadAllText(Path.Combine(assets,"min/vs/loader.js"))); Step("DiagnosticTimerTrace", "globalThis.__xfeTimerCalls=[];const __xfeNativeTimeout=setTimeout;globalThis.setTimeout=function(callback,...args){__xfeTimerCalls.push({type:typeof callback,hostType:__xfeTimerType(callback),text:String(callback).slice(0,100)});return __xfeNativeTimeout(callback,...args);};"); Step("RequireUnmodifiedEditorWithRealCss", "require.config({paths:{vs:'http://xfe.test/Monaco/min/vs'}});require(['vs/editor/editor.main'],function(){globalThis.__xfeLoaded=true;},function(error){globalThis.__xfeLoadError=String(error);});"); PumpResources(); Step("EntryResolved", "if(!globalThis.__xfeLoaded)throw new Error(globalThis.__xfeLoadError||'AMD entry not resolved');typeof monaco.editor.create;"); if(Step("CreateRealEditor", "globalThis.__xfeEditor=monaco.editor.create(document.getElementById('container'),{value:'const answer = 42;',language:'plaintext',automaticLayout:false,minimap:{enabled:false},dimension:{width:800,height:600},accessibilitySupport:'off'});")) { Step("ValueRoundTrip", "if(__xfeEditor.getValue()!=='const answer = 42;')throw new Error('Initial model mismatch');__xfeEditor.setValue('const changed = 43;');if(__xfeEditor.getValue()!=='const changed = 43;')throw new Error('setValue/getValue mismatch');__xfeEditor.getValue();"); Step("EditingAction", "__xfeEditor.setPosition({lineNumber:1,column:1});__xfeEditor.trigger('keyboard','type',{text:'// '});if(!__xfeEditor.getValue().startsWith('// '))throw new Error('Editor typing action did not change model');__xfeEditor.getValue();"); PumpResources(); } } catch(Exception error) { stages.Add(new {Name="ProbeHostFailure",Success=false,Error=error.ToString()}); Console.WriteLine(error); } File.WriteAllText(Path.Combine(output,"Document.html"),document.DocumentElement?.OuterHtml ?? ""); var receipt=new { Kind="RealMonacoCurrentDomProbe",MonacoVersion="0.52.2",JintVersion="4.11.0",ProbeOnly=true, NativeGuestTested=false,HostBrowserUsed=false,FakeMonacoObjects=false,CssPluginDisabled=false, CssResourcesRead=sheets.Count,NativeCssLayoutConnected=false,DomMutations=mutations,DomElements=document.QuerySelectorAll("*").Length, SnapshotPath=snapshot,Snapshot=Directory.GetFiles(snapshot,"*.cs").Select(p=>new{Name=Path.GetFileName(p),Sha256=Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(p)))}).ToArray(), Resources=resourceLog,Stages=stages }; var receiptPath=Path.Combine(output,"Result.json"); File.WriteAllText(receiptPath,JsonSerializer.Serialize(receipt,new JsonSerializerOptions{WriteIndented=true})); Console.WriteLine(receiptPath);