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<string, string> 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<string, string>(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--;
}
}
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<string, string> 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<string, string>(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--;
}
}