diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09d0aed..87d88e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,19 @@
+# 1.1.3
+
+Released on Friday, October 30 2026.
+
+- Fixed prepared regular-expression literals ignoring the timeout configured during engine creation
+- Fixed the console only having `log`; it now has all methods of the console standard (e.g. `error`, `warn`, `info`, `debug`, `assert`, `count`, `time`), as scripts calling or wrapping one of them failed
+- Fixed reading the globals of scripts (e.g. `var x`, `x = 1`) and the built-ins (e.g. `Promise`, `JSON`) through `window`, which gave `undefined`
+- Fixed named indexers (e.g. `item` of `NodeList` and `HTMLCollection`, `namedItem`) being exposed as properties instead of methods
+- Fixed functions passed for delegates with value-type or no parameters failing to convert
+- Fixed errors thrown by DOM members losing their message
+- Fixed only the last of several extension methods of the same name (e.g. `scrollTo(x, y)` and `scrollTo(options)`) being callable; the one fitting the arguments is called
+- Fixed the geometry stubs of this library (e.g. `offsetHeight` always 0) hiding the implementation of another library (e.g. AngleSharp.Renderer)
+- Fixed plain objects not converting to dictionaries (classes with `DomName` members, e.g. `{ top: 100 }` to `ScrollToOptions`)
+- Fixed `dataset` rejecting camel-cased names (e.g. `dataset.sideNavVisible` for `data-side-nav-visible`) with a syntax error
+- Added `requestAnimationFrame`, `cancelAnimationFrame`, `queueMicrotask`, and `performance` (`now()`, `timeOrigin`) to the window
+
# 1.1.2
Released on Sunday, October 25 2026.
diff --git a/src/AngleSharp.Js.Tests/DomTests.cs b/src/AngleSharp.Js.Tests/DomTests.cs
index 48c02b4..54b92f1 100644
--- a/src/AngleSharp.Js.Tests/DomTests.cs
+++ b/src/AngleSharp.Js.Tests/DomTests.cs
@@ -6,6 +6,17 @@ namespace AngleSharp.Js.Tests
[TestFixture]
public class DomTests
{
+ [TestCase("(function () { var e = document.createElement('div'); e.setAttribute('data-side-nav-visible', 'true'); return e.dataset.sideNavVisible; })()", "true")]
+ [TestCase("(function () { var e = document.createElement('div'); e.dataset.sideNavVisible = 'false'; return e.getAttribute('data-side-nav-visible'); })()", "false")]
+ [TestCase("(function () { var e = document.createElement('div'); e.dataset.level = '2'; return e.getAttribute('data-level'); })()", "2")]
+ [TestCase("(function () { var e = document.createElement('div'); e.setAttribute('data-foo-bar', 'x'); return String(e.dataset['foo-bar']); })()", "undefined")]
+ public async Task DatasetMapsCamelCasedNamesToAttributes(string source, string expected)
+ {
+ // dev.to sets document.body.dataset.sideNavVisible, which used to throw a syntax error
+ var result = await source.EvalScriptAsync();
+ Assert.AreEqual(expected, result);
+ }
+
[Test]
public async Task NodeHasChildNodesIsAFunction()
{
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.Tests/OverloadTests.cs b/src/AngleSharp.Js.Tests/OverloadTests.cs
new file mode 100644
index 0000000..fc0fbce
--- /dev/null
+++ b/src/AngleSharp.Js.Tests/OverloadTests.cs
@@ -0,0 +1,88 @@
+namespace AngleSharp.Js.Tests
+{
+ using AngleSharp.Attributes;
+ using AngleSharp.Dom;
+ using NUnit.Framework;
+ using System;
+ using System.Globalization;
+ using System.Threading.Tasks;
+
+ [DomName("ProbeMode")]
+ public enum ProbeMode
+ {
+ [DomName("near")]
+ Near,
+ [DomName("far")]
+ Far,
+ }
+
+ [DomName("ProbeOptions")]
+ public sealed class ProbeOptions
+ {
+ [DomName("left")]
+ public Double? Left { get; set; }
+
+ [DomName("mode")]
+ public ProbeMode Mode { get; set; } = ProbeMode.Near;
+ }
+
+ [DomExposed("Element")]
+ public static class ProbeElementExtensions
+ {
+ [DomName("probe")]
+ public static String Probe(this IElement element) => "none";
+
+ [DomName("offsetHeight")]
+ [DomAccessor(Accessors.Getter)]
+ public static Int32 GetOffsetHeight(this IElement element) => 42;
+
+ [DomName("probe")]
+ public static String Probe(this IElement element, Double x, Double y) =>
+ String.Format(CultureInfo.InvariantCulture, "xy:{0},{1}", x, y);
+
+ [DomName("probe")]
+ public static String Probe(this IElement element, Boolean flag) => "flag:" + flag;
+
+ [DomName("probe")]
+ public static String Probe(this IElement element, ProbeOptions options) =>
+ String.Format(CultureInfo.InvariantCulture, "options:{0},{1}", options.Left?.ToString(CultureInfo.InvariantCulture) ?? "-", options.Mode);
+ }
+
+ ///
x
")); + + Assert.AreEqual(expected, document.ExecuteScript(script)?.ToString()); + } + + [TestCase("document.querySelectorAll('p').item(1).id", "b")] + [TestCase("document.getElementsByTagName('p').item(0).id", "a")] + [TestCase("document.body.children.item(1).id", "b")] + [TestCase("document.body.children.namedItem('b').id", "b")] + [TestCase("document.querySelector('p').attributes.item(0).name", "id")] + [TestCase("typeof NodeList.prototype.item", "function")] + public async Task NamedIndexersAreMethods(String script, String expected) + { + var context = BrowsingContext.New(Configuration.Default.WithJs()); + var document = await context.OpenAsync(m => m.Content("")); + + Assert.AreEqual(expected, document.ExecuteScript(script)?.ToString()); + } + + [Test] + public async Task ErrorOfAMemberKeepsItsMessage() + { + var result = await "(function () { try { document.querySelector('!'); } catch (e) { return e.message; } })()".EvalScriptAsync(); + Assert.IsNotEmpty(result); + } + + [Test] + public async Task PerformanceNowIsMonotonic() + { + var result = await "(function () { var a = performance.now(); var b = performance.now(); return (a >= 0 && b >= a && typeof performance.timeOrigin === 'number'); })()".EvalScriptAsync(); + Assert.AreEqual("True", result); + } + + [Test] + public async Task AnimationFrameCallbackReceivesTheWindowAndATime() + { + var context = BrowsingContext.New(Configuration.Default.WithJs().WithEventLoop()); + var document = await context.OpenAsync(m => m.Content("")); + + for (var i = 0; i < 100 && document.Title.Length == 0; i++) + { + await Task.Delay(20); + } + + Assert.AreEqual("true,number", document.Title); + } + + [Test] + public async Task CancelledAnimationFrameDoesNotRun() + { + var context = BrowsingContext.New(Configuration.Default.WithJs().WithEventLoop()); + var document = await context.OpenAsync(m => m.Content("")); + + for (var i = 0; i < 100 && document.Title.Length == 0; i++) + { + await Task.Delay(20); + } + + Assert.AreEqual("done", document.Title); + } + + [Test] + public async Task QueuedMicrotaskRunsAfterTheScript() + { + var context = BrowsingContext.New(Configuration.Default.WithJs().WithEventLoop()); + var document = await context.OpenAsync(m => m.Content("")); + + for (var i = 0; i < 100 && document.Title != "abc"; i++) + { + await Task.Delay(20); + } + + Assert.AreEqual("abc", document.Title); + } + } +} diff --git a/src/AngleSharp.Js/Cache/DomShapeCache.cs b/src/AngleSharp.Js/Cache/DomShapeCache.cs index 0dc7399..fc229da 100644 --- a/src/AngleSharp.Js/Cache/DomShapeCache.cs +++ b/src/AngleSharp.Js/Cache/DomShapeCache.cs @@ -172,7 +172,8 @@ public DomShape Build() } var indexers = _numericIndexer != null || _stringIndexerGetter != null || _stringIndexerSetter != null - ? new DomIndexers(_numericIndexer, _stringIndexerGetter, _stringIndexerSetter) + ? new DomIndexers(_numericIndexer, _stringIndexerGetter, _stringIndexerSetter, + typeof(IStringMap).IsAssignableFrom(_type) ? DomIndexers.ToDatasetName : null) : null; return new DomShape(_type, _baseType, _name, builder.Build(), indexers, constructor); @@ -196,7 +197,12 @@ private void SetExtensionMembers() return; } - foreach (var type in _libs.Assemblies.GetExtensionTypes(_name)) + var own = typeof(DomShapeBuilder).GetTypeInfo().Assembly; + var types = _libs.Assemblies.GetExtensionTypes(_name).ToList(); + + // The members this library brings itself are partly stubs standing in for a missing + // layout (offsetHeight is 0, ...), so a library implementing them (the renderer) wins. + foreach (var type in types.Where(m => m.GetTypeInfo().Assembly != own).Concat(types.Where(m => m.GetTypeInfo().Assembly == own))) { var typeInfo = type.GetTypeInfo(); SetExtensionMethods(typeInfo.DeclaredMethods); @@ -246,7 +252,10 @@ private void SetExtensionMethods(IEnumerable