using Jint.Native; using Jint.Native.Object; using Jint.Runtime.Interop; namespace XFESystem.Browser; public sealed partial class DomBridge { private readonly Queue<(int Id, string Address, JsValue Callback, IReadOnlyDictionary Headers)> networkRequests = []; private int nextNetworkRequestId; internal bool HasNetworkRequests => networkRequests.Count != 0; internal int NetworkRequestCount => networkRequests.Count; private void InstallNetworkCallbacks() { engine.SetValue("__xfeHttpRequest", new ClrFunction(engine, "request", (_, args) => { if (networkRequests.Count >= limits.MaximumPendingTasks) throw new BrowserLimitException("The browser request queue is full."); var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); if (Value(args, 2) is JsArray fields) { if (fields.Length > 32) throw new BrowserLimitException("The script request header count limit was reached."); for (uint index = 0; index < fields.Length; index++) { if (fields[index] is not JsArray pair || pair.Length != 2) throw new ArgumentException("Invalid request header pair."); string name = LimitedText(pair[0]), value = LimitedText(pair[1]); if (BrowserRequestHeaders.Allowed(name, value)) headers[name] = value; } } int id = checked(++nextNetworkRequestId); networkRequests.Enqueue((id, Argument(args, 0), Value(args, 1), BrowserRequestHeaders.Copy(headers))); return id; })); engine.SetValue("__xfeHttpCancel", new ClrFunction(engine, "cancelRequest", (_, args) => { int id = NumberIndex(Value(args, 0)), count = networkRequests.Count; for (int index = 0; index < count; index++) { var request = networkRequests.Dequeue(); if (request.Id != id) networkRequests.Enqueue(request); } return JsValue.Undefined; })); } internal void ProcessNetworkRequest() { if (!networkRequests.TryDequeue(out var request)) return; ObjectInstance? value = null; string? error = null; try { var response = host.GetResourceWithHeaders?.Invoke(request.Address, request.Headers) ?? (request.Headers.Count == 0 ? host.GetResource?.Invoke(request.Address) : null) ?? throw new NotSupportedException("No page network service for these request headers."); value = new BrowserHostObject(engine); Data(value, "status", response.StatusCode); Data(value, "url", response.Address.AbsoluteUri); Data(value, "text", response.Body); Data(value, "contentType", response.ContentType); } catch (Exception exception) when (exception is not OutOfMemoryException and not StackOverflowException) { error = exception.Message; } // Network I/O has completed before entering a fresh bounded callback. // No nested Engine.Invoke resets the budget inside an author evaluation. callbackDepth++; characterBudgetValid = false; try { engine.Invoke(request.Callback, engine.Global, [(JsValue?)value ?? JsValue.Null, error is null ? JsValue.Null : error]); engine.Evaluate(BrowserDomBootstrap.FlushJobs); } catch { callbackDepth--; throw; } callbackDepth--; } }