using AngleSharp.Css.Dom;
using AngleSharp.Css.Parser;
using Jint;
using Jint.Native;
using Jint.Native.Object;
namespace XFESystem.Browser;
public sealed partial class DomBridge
{
private readonly Dictionary<int, (ICssStyleSheet Sheet, int Characters)> constructedSheets = [];
private readonly CssParser constructedCssParser = new(new CssParserOptions { IsIncludingUnknownDeclarations = true, IsIncludingUnknownRules = true });
private int nextSheetId, constructedCssCharacters;
private void InstallCssomCallbacks()
{
var callbacks = new BrowserHostObject(engine);
Function(callbacks, "create", _ =>
{
if (constructedSheets.Count >= limits.MaximumStyleSheets) throw new BrowserLimitException("The constructed stylesheet count limit was reached.");
var id = checked(++nextSheetId); constructedSheets.Add(id, (constructedCssParser.ParseStyleSheet(string.Empty), 0)); return id;
});
Function(callbacks, "rules", args =>
{
var sheet = Sheet(Value(args, 0));
return ListValues(sheet.Sheet.Rules.Select(rule => (JsValue)rule.CssText));
});
Function(callbacks, "replace", args =>
{
var id = SheetId(Value(args, 0)); var previous = constructedSheets[id]; var text = LimitedText(Value(args, 1));
CheckCssCharacters(text.Length - previous.Characters);
var replacement = constructedCssParser.ParseStyleSheet(text); CheckCssRules(replacement);
constructedSheets[id] = (replacement, text.Length); constructedCssCharacters += text.Length - previous.Characters; return JsValue.Undefined;
});
Function(callbacks, "insert", args =>
{
var id = SheetId(Value(args, 0)); var previous = constructedSheets[id]; var text = LimitedText(Value(args, 1));
var index = CssIndex(Value(args, 2), previous.Sheet.Rules.Length, allowEnd: true);
var parsed = constructedCssParser.ParseStyleSheet(text);
if (parsed.Rules.Length != 1) throw AdjacentError("SyntaxError", "insertRule requires exactly one CSS rule.", 12);
CheckCssCharacters(text.Length);
if (previous.Sheet.Rules.Length >= 4096) throw new BrowserLimitException("The CSSOM rule limit was reached.");
try { previous.Sheet.Insert(text, index); }
catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
{ throw AdjacentError("SyntaxError", error.Message, 12); }
constructedSheets[id] = (previous.Sheet, previous.Characters + text.Length); constructedCssCharacters += text.Length; return index;
});
Function(callbacks, "delete", args =>
{
var id = SheetId(Value(args, 0)); var previous = constructedSheets[id];
var index = CssIndex(Value(args, 1), previous.Sheet.Rules.Length, allowEnd: false);
previous.Sheet.RemoveAt(index);
// Serialize after deletion so the retained budget reflects actual
// remaining content rather than a potentially different input form.
var characters = previous.Sheet.Rules.Sum(rule => rule.CssText.Length);
constructedSheets[id] = (previous.Sheet, characters); constructedCssCharacters += characters - previous.Characters;
return JsValue.Undefined;
});
engine.SetValue("__xfeCssom", callbacks);
}
private int SheetId(JsValue value)
{
if (!value.IsNumber() || value.AsNumber() != Math.Truncate(value.AsNumber()) || value.AsNumber() > int.MaxValue ||
value.AsNumber() <= 0 || !constructedSheets.ContainsKey((int)value.AsNumber())) throw new ArgumentException("Expected this document's constructed stylesheet.");
return (int)value.AsNumber();
}
private (ICssStyleSheet Sheet, int Characters) Sheet(JsValue value) => constructedSheets[SheetId(value)];
private void CheckCssCharacters(int difference)
{ if (difference > limits.MaximumStyleCharacters - constructedCssCharacters) throw new BrowserLimitException("The constructed CSS character limit was reached."); }
private static void CheckCssRules(ICssStyleSheet sheet)
{ if (sheet.Rules.Length > 4096) throw new BrowserLimitException("The CSSOM rule limit was reached."); }
private Jint.Runtime.JavaScriptException CssIndexError() => AdjacentError("IndexSizeError", "The CSS rule index is outside the stylesheet.", 1);
private int CssIndex(JsValue value, int count, bool allowEnd)
{
var number = value.IsUndefined() ? 0 : Number(value);
if (!double.IsFinite(number) || number < 0 || number > uint.MaxValue) throw CssIndexError();
var index = (long)Math.Truncate(number);
if (index > count || !allowEnd && index == count) throw CssIndexError();
return (int)index;
}
}
using AngleSharp.Css.Dom;
using AngleSharp.Css.Parser;
using Jint;
using Jint.Native;
using Jint.Native.Object;
namespace XFESystem.Browser;
public sealed partial class DomBridge
{
private readonly Dictionary<int, (ICssStyleSheet Sheet, int Characters)> constructedSheets = [];
private readonly CssParser constructedCssParser = new(new CssParserOptions { IsIncludingUnknownDeclarations = true, IsIncludingUnknownRules = true });
private int nextSheetId, constructedCssCharacters;
private void InstallCssomCallbacks()
{
var callbacks = new BrowserHostObject(engine);
Function(callbacks, "create", _ =>
{
if (constructedSheets.Count >= limits.MaximumStyleSheets) throw new BrowserLimitException("The constructed stylesheet count limit was reached.");
var id = checked(++nextSheetId); constructedSheets.Add(id, (constructedCssParser.ParseStyleSheet(string.Empty), 0)); return id;
});
Function(callbacks, "rules", args =>
{
var sheet = Sheet(Value(args, 0));
return ListValues(sheet.Sheet.Rules.Select(rule => (JsValue)rule.CssText));
});
Function(callbacks, "replace", args =>
{
var id = SheetId(Value(args, 0)); var previous = constructedSheets[id]; var text = LimitedText(Value(args, 1));
CheckCssCharacters(text.Length - previous.Characters);
var replacement = constructedCssParser.ParseStyleSheet(text); CheckCssRules(replacement);
constructedSheets[id] = (replacement, text.Length); constructedCssCharacters += text.Length - previous.Characters; return JsValue.Undefined;
});
Function(callbacks, "insert", args =>
{
var id = SheetId(Value(args, 0)); var previous = constructedSheets[id]; var text = LimitedText(Value(args, 1));
var index = CssIndex(Value(args, 2), previous.Sheet.Rules.Length, allowEnd: true);
var parsed = constructedCssParser.ParseStyleSheet(text);
if (parsed.Rules.Length != 1) throw AdjacentError("SyntaxError", "insertRule requires exactly one CSS rule.", 12);
CheckCssCharacters(text.Length);
if (previous.Sheet.Rules.Length >= 4096) throw new BrowserLimitException("The CSSOM rule limit was reached.");
try { previous.Sheet.Insert(text, index); }
catch (Exception error) when (error is not OutOfMemoryException and not StackOverflowException)
{ throw AdjacentError("SyntaxError", error.Message, 12); }
constructedSheets[id] = (previous.Sheet, previous.Characters + text.Length); constructedCssCharacters += text.Length; return index;
});
Function(callbacks, "delete", args =>
{
var id = SheetId(Value(args, 0)); var previous = constructedSheets[id];
var index = CssIndex(Value(args, 1), previous.Sheet.Rules.Length, allowEnd: false);
previous.Sheet.RemoveAt(index);
// Serialize after deletion so the retained budget reflects actual
// remaining content rather than a potentially different input form.
var characters = previous.Sheet.Rules.Sum(rule => rule.CssText.Length);
constructedSheets[id] = (previous.Sheet, characters); constructedCssCharacters += characters - previous.Characters;
return JsValue.Undefined;
});
engine.SetValue("__xfeCssom", callbacks);
}
private int SheetId(JsValue value)
{
if (!value.IsNumber() || value.AsNumber() != Math.Truncate(value.AsNumber()) || value.AsNumber() > int.MaxValue ||
value.AsNumber() <= 0 || !constructedSheets.ContainsKey((int)value.AsNumber())) throw new ArgumentException("Expected this document's constructed stylesheet.");
return (int)value.AsNumber();
}
private (ICssStyleSheet Sheet, int Characters) Sheet(JsValue value) => constructedSheets[SheetId(value)];
private void CheckCssCharacters(int difference)
{ if (difference > limits.MaximumStyleCharacters - constructedCssCharacters) throw new BrowserLimitException("The constructed CSS character limit was reached."); }
private static void CheckCssRules(ICssStyleSheet sheet)
{ if (sheet.Rules.Length > 4096) throw new BrowserLimitException("The CSSOM rule limit was reached."); }
private Jint.Runtime.JavaScriptException CssIndexError() => AdjacentError("IndexSizeError", "The CSS rule index is outside the stylesheet.", 1);
private int CssIndex(JsValue value, int count, bool allowEnd)
{
var number = value.IsUndefined() ? 0 : Number(value);
if (!double.IsFinite(number) || number < 0 || number > uint.MaxValue) throw CssIndexError();
var index = (long)Math.Truncate(number);
if (index > count || !allowEnd && index == count) throw CssIndexError();
return (int)index;
}
}