diff --git a/CHANGELOG.md b/CHANGELOG.md index e0b7d77..a0691b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.1 + +- Fixed canonical DOM node identity and concrete prototypes through `DomSameObject` getters (#140) + # 1.1.0 Released on Saturday, September 5 2026. diff --git a/src/AngleSharp.Js.Tests/SameObjectIdentityTests.cs b/src/AngleSharp.Js.Tests/SameObjectIdentityTests.cs new file mode 100644 index 0000000..9fdfff1 --- /dev/null +++ b/src/AngleSharp.Js.Tests/SameObjectIdentityTests.cs @@ -0,0 +1,31 @@ +namespace AngleSharp.Js.Tests +{ + using AngleSharp.Dom; + using AngleSharp.Scripting; + using Jint; + using NUnit.Framework; + using System.Threading.Tasks; + + [TestFixture] + public class SameObjectIdentityTests + { + [Test] + public async Task MutationTargetUsesTheCanonicalNodeAndItsConcretePrototype() + { + using var context = BrowsingContext.New(Configuration.Default.WithJs()); + var document = await context.OpenAsync(response => response.Content("test")).ConfigureAwait(false); + IMutationRecord record = null; + var observer = new MutationObserver((records, _) => record = records[0]); + observer.Connect(document.Body, attributes: true); + document.Body.SetAttribute("data-value", "changed"); + observer.Disconnect(); + Assert.IsNotNull(record); + var engine = context.GetService().GetOrCreateJint(document); + engine.SetValue("observedRecord", record); + engine.Execute("document.body.marker = 'retained'"); + Assert.AreEqual("target", engine.Evaluate("observedRecord.target.id").ToString()); + Assert.IsTrue(engine.Evaluate("observedRecord.target === document.body").AsBoolean()); + Assert.AreEqual("retained", engine.Evaluate("observedRecord.target.marker").ToString()); + } + } +} diff --git a/src/AngleSharp.Js/EngineInstance.cs b/src/AngleSharp.Js/EngineInstance.cs index 00b67e5..d9ef5a2 100644 --- a/src/AngleSharp.Js/EngineInstance.cs +++ b/src/AngleSharp.Js/EngineInstance.cs @@ -122,6 +122,13 @@ public JsValue GetSameObject(Object owner, MethodInfo getter) return JsValue.Null; } + // Nodes and windows already have a canonical proxy. Preserve its identity, + // concrete prototype and script-owned properties across SameObject getters. + if (current is INode || current is IWindow) + { + return GetDomNode(current); + } + var cache = _sameObjects.GetValue(owner, _ => new SameObjectCache()); return cache.GetOrUpdate(getter, current, () => CreateInstance(current, getter.ReturnType)); }