Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/AngleSharp.Js.Docs/package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
40 changes: 40 additions & 0 deletions src/AngleSharp.Js.Tests/GlobalScopeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace AngleSharp.Js.Tests
{
using NUnit.Framework;
using System;
using System.Threading.Tasks;

/// <summary>
/// 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.
/// </summary>
[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("<!doctype html><p>x</p>"));

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("<!doctype html><p>x</p>"));

Assert.AreEqual("function", document.ExecuteScript("typeof setTimeout"));
Assert.AreEqual("function", document.ExecuteScript("typeof Node"));
Assert.AreEqual("P", document.ExecuteScript("document.querySelector('p').tagName"));
}
}
}
13 changes: 11 additions & 2 deletions src/AngleSharp.Js/EngineInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,12 +70,12 @@ public EngineInstance(IWindow window, IDictionary<String, Object> 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;
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -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. -->
<PropertyGroup Label="Versioning">
<Version>1.1.1</Version>
<Version>1.1.2</Version>
</PropertyGroup>

<PropertyGroup Label="Compilation">
Expand Down
Loading