From c2ffca1ad3f9808423fcedb41a95f616005ca484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20K=C5=82ys?= Date: Fri, 30 Oct 2026 02:49:34 +0100 Subject: [PATCH 1/2] Honor configured regex timeouts in prepared scripts --- CHANGELOG.md | 4 ++ .../EngineConfigurationTests.cs | 46 +++++++++++++++++++ src/AngleSharp.Js/Cache/ScriptCache.cs | 40 ++++++++++++---- src/AngleSharp.Js/EngineInstance.cs | 4 +- 4 files changed, 85 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09d0aed..f1b4f45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.3 + +- Fixed prepared regular-expression literals ignoring the timeout configured during engine creation + # 1.1.2 Released on Sunday, October 25 2026. diff --git a/src/AngleSharp.Js.Tests/EngineConfigurationTests.cs b/src/AngleSharp.Js.Tests/EngineConfigurationTests.cs index cc69a88..3841145 100644 --- a/src/AngleSharp.Js.Tests/EngineConfigurationTests.cs +++ b/src/AngleSharp.Js.Tests/EngineConfigurationTests.cs @@ -5,6 +5,7 @@ namespace AngleSharp.Js.Tests using AngleSharp.Js.Tests.Mocks; using AngleSharp.Scripting; using Jint; + using Jint.Native; using Jint.Runtime; using NUnit.Framework; using System; @@ -14,6 +15,51 @@ namespace AngleSharp.Js.Tests public class EngineConfigurationTests { + [TestCase(false)] + [TestCase(true)] + public async Task RegexTimeoutAppliesToInlineAndExplicitScripts(Boolean inline) + { + var timeout = TimeSpan.FromMilliseconds(37); + var config = Configuration.Default + .With(new EngineCreator((window, options) => new Engine(options.RegexTimeoutInterval(timeout)))) + .WithJs() + .WithEventLoop(); + + using (var context = BrowsingContext.New(config)) + { + var document = await context.OpenAsync(request => request.Content(inline + ? "" + : "")).ConfigureAwait(false); + var expression = (JsRegExp)document.ExecuteScript(inline ? "configuredRegex" : "/explicitEngineTimeout/"); + + Assert.AreEqual(timeout, expression.Value.MatchTimeout); + } + } + + [TestCase(23, 71)] + [TestCase(71, 23)] + public async Task CachedScriptsKeepEachEngineRegexTimeout(Int32 firstMilliseconds, Int32 secondMilliseconds) + { + var source = "/cachedEngineTimeout" + firstMilliseconds + "/"; + + foreach (var milliseconds in new[] { firstMilliseconds, secondMilliseconds, firstMilliseconds }) + { + var timeout = TimeSpan.FromMilliseconds(milliseconds); + var config = Configuration.Default + .With(new EngineCreator((window, options) => new Engine(options.RegexTimeoutInterval(timeout)))) + .WithJs() + .WithEventLoop(); + + using (var context = BrowsingContext.New(config)) + { + var document = await context.OpenNewAsync().ConfigureAwait(false); + var expression = (JsRegExp)document.ExecuteScript(source); + + Assert.AreEqual(timeout, expression.Value.MatchTimeout); + } + } + } + [Test] public async Task ConstraintsAreInstalledBeforeInlineScriptsRun() { diff --git a/src/AngleSharp.Js/Cache/ScriptCache.cs b/src/AngleSharp.Js/Cache/ScriptCache.cs index 99faff4..8a63912 100644 --- a/src/AngleSharp.Js/Cache/ScriptCache.cs +++ b/src/AngleSharp.Js/Cache/ScriptCache.cs @@ -9,8 +9,8 @@ namespace AngleSharp.Js.Cache /// /// Caches the parsed and analyzed form of scripts. A prepared script is /// documented by Jint as reusable and thread-safe, so the same one can serve - /// every document that runs the same source - a page loading a library ends up - /// parsing it once instead of once per document. + /// every document that runs the same source with the same regex timeout - a + /// page loading a library ends up parsing it once instead of once per document. /// static class ScriptCache { @@ -19,24 +19,29 @@ static class ScriptCache // seen first: libraries are referenced early and by many documents. private const Int32 Capacity = 32; - private static readonly ConcurrentDictionary> _scripts = - new ConcurrentDictionary>(StringComparer.Ordinal); + private static readonly ConcurrentDictionary> _scripts = + new ConcurrentDictionary>(); /// /// Gets the prepared form of the given source. An invalid result means the /// source could not be prepared and should be handed to the engine as text, /// so that the engine reports the syntax error itself. /// - public static Prepared