diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bfc820..09d0aed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 1.1.2
+
+Released on Sunday, October 25 2026.
+
+- Fixed globals named like a member of the window (e.g. `var item`, `var open`, `function close() {}`) keeping that member, as the members were copied to the global scope as read-only
+
# 1.1.1
Released on Wednesday, October 7 2026.
diff --git a/src/AngleSharp.Js.Docs/package.json b/src/AngleSharp.Js.Docs/package.json
index b7ca4a5..820ca56 100644
--- a/src/AngleSharp.Js.Docs/package.json
+++ b/src/AngleSharp.Js.Docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@anglesharp/js",
- "version": "1.1.1",
+ "version": "1.1.2",
"preview": true,
"description": "The doclet for the AngleSharp.Js documentation.",
"keywords": [
diff --git a/src/AngleSharp.Js.Tests/GlobalScopeTests.cs b/src/AngleSharp.Js.Tests/GlobalScopeTests.cs
new file mode 100644
index 0000000..0aa8555
--- /dev/null
+++ b/src/AngleSharp.Js.Tests/GlobalScopeTests.cs
@@ -0,0 +1,40 @@
+namespace AngleSharp.Js.Tests
+{
+ using NUnit.Framework;
+ using System;
+ using System.Threading.Tasks;
+
+ ///
+ /// The members of the window are configurable and writable, so a page's own globals replace
+ /// them as in a browser - a script declaring var item lost to window's item(), and every use
+ /// of it failed.
+ ///
+ [TestFixture]
+ public class GlobalScopeTests
+ {
+ [TestCase("var item = 1; typeof item", "number")]
+ [TestCase("var open = 'x'; typeof open", "string")]
+ [TestCase("var stop = 1; stop + 1", "2")]
+ [TestCase("function close() { return 'mine'; } close()", "mine")]
+ [TestCase("var Node = 5; typeof Node", "number")]
+ [TestCase("var unrelated = 1; typeof unrelated", "number")]
+ public async Task GlobalsNamedLikeWindowMembersReplaceThem(String script, String expected)
+ {
+ var context = BrowsingContext.New(Configuration.Default.WithJs());
+ var document = await context.OpenAsync(m => m.Content("
x
"));
+
+ Assert.AreEqual(expected, document.ExecuteScript(script)?.ToString());
+ }
+
+ [Test]
+ public async Task WindowMembersStayAvailableWithoutOwnGlobals()
+ {
+ var context = BrowsingContext.New(Configuration.Default.WithJs());
+ var document = await context.OpenAsync(m => m.Content("x
"));
+
+ Assert.AreEqual("function", document.ExecuteScript("typeof setTimeout"));
+ Assert.AreEqual("function", document.ExecuteScript("typeof Node"));
+ Assert.AreEqual("P", document.ExecuteScript("document.querySelector('p').tagName"));
+ }
+ }
+}
diff --git a/src/AngleSharp.Js/EngineInstance.cs b/src/AngleSharp.Js/EngineInstance.cs
index 24a9856..a027ca1 100644
--- a/src/AngleSharp.Js/EngineInstance.cs
+++ b/src/AngleSharp.Js/EngineInstance.cs
@@ -9,6 +9,7 @@ namespace AngleSharp.Js
using Jint.Native.Json;
using Jint.Native.Object;
using Jint.Runtime;
+ using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
using System;
using System.Collections.Generic;
@@ -69,12 +70,12 @@ public EngineInstance(IWindow window, IDictionary assignments, I
foreach (var property in _window.GetOwnProperties())
{
- _engine.Global.FastSetProperty(property.Key.ToString(), property.Value);
+ _engine.Global.FastSetProperty(property.Key.ToString(), AsGlobalProperty(property.Value));
}
foreach (var prototypeProperty in Window.Prototype.GetOwnProperties())
{
- _engine.Global.FastSetProperty(prototypeProperty.Key.ToString(), prototypeProperty.Value);
+ _engine.Global.FastSetProperty(prototypeProperty.Key.ToString(), AsGlobalProperty(prototypeProperty.Value));
}
_engine.Global.Prototype = _window.Prototype;
@@ -249,6 +250,14 @@ private JsValue ImportModule(String specifier, String source)
#region Helpers
+ // The members of the window are configurable (and its operations and interface objects
+ // writable) per WebIDL, so that a page's own global replaces them as in a browser, e.g.
+ // var item = ..., var open = ... or var Node = ...; an accessor like name keeps its setter.
+ private static PropertyDescriptor AsGlobalProperty(PropertyDescriptor descriptor) =>
+ descriptor.IsAccessorDescriptor()
+ ? new GetSetPropertyDescriptor(descriptor.Get, descriptor.Set, descriptor.Enumerable, true)
+ : new PropertyDescriptor(descriptor.Value, true, descriptor.Enumerable, true);
+
// A collection is projected by the array-like proxy, which lets the engine read its
// indices and length directly; everything else by the ordinary one. The decision is a
// property of the type, so it is resolved once per type for the whole process.
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index dc8033a..4063434 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -10,7 +10,7 @@
entry of CHANGELOG.md, which build/Build.cs passes in as -p:Version, so this value only
affects a plain `dotnet build` and no longer needs bumping for a release. -->
- 1.1.1
+ 1.1.2